1use alloc::{format, string::String, vec::Vec};
2
3pub use crate::arch::x86_64::pci::{
4 class, command, config, device, intel_eth, net_subclass, sata_progif, storage_subclass, vendor,
5};
6use crate::{
7 arch::x86_64::pci as arch_pci,
8 vfs::{self, OpenFlags},
9};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum Bar {
13 Io { port: u16 },
14 Memory32 { addr: u32, prefetchable: bool },
15 Memory64 { addr: u64, prefetchable: bool },
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct PciAddress {
20 pub bus: u8,
21 pub device: u8,
22 pub function: u8,
23}
24
25impl PciAddress {
26 pub const fn new(bus: u8, device: u8, function: u8) -> Self {
28 Self {
29 bus,
30 device,
31 function,
32 }
33 }
34}
35
36#[derive(Debug, Clone, Copy)]
37pub struct PciDevice {
38 pub address: PciAddress,
39 pub vendor_id: u16,
40 pub device_id: u16,
41 pub class_code: u8,
42 pub subclass: u8,
43 pub prog_if: u8,
44 pub revision: u8,
45 pub header_type: u8,
46 pub interrupt_line: u8,
47 pub interrupt_pin: u8,
48}
49
50#[derive(Debug, Clone, Copy, Default)]
51pub struct ProbeCriteria {
52 pub vendor_id: Option<u16>,
53 pub device_id: Option<u16>,
54 pub class_code: Option<u8>,
55 pub subclass: Option<u8>,
56 pub prog_if: Option<u8>,
57}
58
59impl ProbeCriteria {
60 pub const fn any() -> Self {
62 Self {
63 vendor_id: None,
64 device_id: None,
65 class_code: None,
66 subclass: None,
67 prog_if: None,
68 }
69 }
70
71 fn matches(&self, dev: &PciDevice) -> bool {
73 if self.vendor_id.is_some_and(|v| dev.vendor_id != v) {
74 return false;
75 }
76 if self.device_id.is_some_and(|d| dev.device_id != d) {
77 return false;
78 }
79 if self.class_code.is_some_and(|c| dev.class_code != c) {
80 return false;
81 }
82 if self.subclass.is_some_and(|s| dev.subclass != s) {
83 return false;
84 }
85 if self.prog_if.is_some_and(|p| dev.prog_if != p) {
86 return false;
87 }
88 true
89 }
90}
91
92fn open_read_all(path: &str) -> Option<Vec<u8>> {
94 let fd = vfs::open(path, OpenFlags::READ).ok()?;
95 let mut out = Vec::new();
96 let mut chunk = [0u8; 256];
97 loop {
98 let n = match vfs::read(fd, &mut chunk) {
99 Ok(n) => n,
100 Err(_) => {
101 let _ = vfs::close(fd);
102 return None;
103 }
104 };
105 if n == 0 {
106 break;
107 }
108 out.extend_from_slice(&chunk[..n]);
109 }
110 let _ = vfs::close(fd);
111 Some(out)
112}
113
114fn open_write(path: &str, bytes: &[u8]) -> bool {
116 let fd = match vfs::open(path, OpenFlags::WRITE) {
117 Ok(fd) => fd,
118 Err(_) => return false,
119 };
120 let ok = vfs::write(fd, bytes).is_ok();
121 let _ = vfs::close(fd);
122 ok
123}
124
125fn parse_hex_u8(s: &str) -> Option<u8> {
127 u8::from_str_radix(s.trim_start_matches("0x"), 16).ok()
128}
129
130fn parse_hex_u16(s: &str) -> Option<u16> {
132 u16::from_str_radix(s.trim_start_matches("0x"), 16).ok()
133}
134
135fn parse_inventory_line(line: &str) -> Option<PciDevice> {
137 let mut parts = line.split_whitespace();
138 let bdf = parts.next()?;
139 let ids = parts.next()?;
140 let class_sub = parts.next()?;
141 let prog_if = parts.next()?;
142 let rev = parts.next()?;
143 let irq = parts.next()?;
144
145 let (bus_s, rest) = bdf.split_once(':')?;
146 let (dev_s, fun_s) = rest.split_once('.')?;
147 let bus = parse_hex_u8(bus_s)?;
148 let device = parse_hex_u8(dev_s)?;
149 let function = fun_s.parse::<u8>().ok()?;
150
151 let (ven_s, did_s) = ids.split_once(':')?;
152 let vendor_id = parse_hex_u16(ven_s)?;
153 let device_id = parse_hex_u16(did_s)?;
154
155 let (class_s, sub_s) = class_sub.split_once(':')?;
156 let class_code = parse_hex_u8(class_s)?;
157 let subclass = parse_hex_u8(sub_s)?;
158
159 let prog_if = parse_hex_u8(prog_if)?;
160 let revision = parse_hex_u8(rev)?;
161 let interrupt_line = irq.parse::<u8>().unwrap_or(0);
162
163 Some(PciDevice {
164 address: PciAddress::new(bus, device, function),
165 vendor_id,
166 device_id,
167 class_code,
168 subclass,
169 prog_if,
170 revision,
171 header_type: 0,
172 interrupt_line,
173 interrupt_pin: 0,
174 })
175}
176
177fn all_devices_from_bus_service() -> Vec<PciDevice> {
179 let bytes = match open_read_all("/bus/pci/inventory") {
180 Some(b) => b,
181 None => return Vec::new(),
182 };
183 let text = match core::str::from_utf8(&bytes) {
184 Ok(s) => s,
185 Err(_) => return Vec::new(),
186 };
187 let mut out = Vec::new();
188 for (idx, line) in text.lines().enumerate() {
189 if idx == 0 || line.trim().is_empty() {
190 continue;
191 }
192 if let Some(dev) = parse_inventory_line(line) {
193 out.push(dev);
194 }
195 }
196 out
197}
198
199fn cfg_path(addr: PciAddress, offset: u8, width: u8) -> String {
201 format!(
202 "/bus/pci/cfg/{:02x}:{:02x}.{:x}/{:02x}/{}",
203 addr.bus, addr.device, addr.function, offset, width
204 )
205}
206
207fn to_arch_addr(addr: PciAddress) -> arch_pci::PciAddress {
209 arch_pci::PciAddress::new(addr.bus, addr.device, addr.function)
210}
211
212fn arch_dev_handle(addr: PciAddress) -> arch_pci::PciDevice {
214 arch_pci::PciDevice {
215 address: to_arch_addr(addr),
216 vendor_id: 0,
217 device_id: 0,
218 class_code: 0,
219 subclass: 0,
220 prog_if: 0,
221 revision: 0,
222 header_type: 0,
223 interrupt_line: 0,
224 interrupt_pin: 0,
225 }
226}
227
228fn cfg_read(addr: PciAddress, offset: u8, width: u8) -> Option<u32> {
230 if let Some(bytes) = open_read_all(&cfg_path(addr, offset, width)) {
231 let text = core::str::from_utf8(&bytes).ok()?.trim();
232 let hex = text.strip_prefix("0x")?;
233 return u32::from_str_radix(hex, 16).ok();
234 }
235
236 let dev = arch_dev_handle(addr);
238 Some(match width {
239 1 => dev.read_config_u8(offset) as u32,
240 2 => dev.read_config_u16(offset) as u32,
241 4 => dev.read_config_u32(offset),
242 _ => return None,
243 })
244}
245
246fn cfg_write(addr: PciAddress, offset: u8, width: u8, value: u32) -> bool {
248 if open_write(&cfg_path(addr, offset, width), &value.to_le_bytes()) {
249 return true;
250 }
251
252 let dev = arch_dev_handle(addr);
254 match width {
255 1 => dev.write_config_u8(offset, value as u8),
256 2 => dev.write_config_u16(offset, value as u16),
257 4 => dev.write_config_u32(offset, value),
258 _ => return false,
259 }
260 true
261}
262
263impl PciDevice {
264 pub fn read_config_u8(&self, offset: u8) -> u8 {
266 cfg_read(self.address, offset, 1).map_or(0, |v| v as u8)
267 }
268
269 pub fn read_config_u16(&self, offset: u8) -> u16 {
271 cfg_read(self.address, offset, 2).map_or(0, |v| v as u16)
272 }
273
274 pub fn read_config_u32(&self, offset: u8) -> u32 {
276 cfg_read(self.address, offset, 4).unwrap_or(0)
277 }
278
279 pub fn write_config_u8(&self, offset: u8, value: u8) {
281 let _ = cfg_write(self.address, offset, 1, value as u32);
282 }
283
284 pub fn write_config_u16(&self, offset: u8, value: u16) {
286 let _ = cfg_write(self.address, offset, 2, value as u32);
287 }
288
289 pub fn write_config_u32(&self, offset: u8, value: u32) {
291 let _ = cfg_write(self.address, offset, 4, value);
292 }
293
294 pub fn read_bar(&self, bar_index: u8) -> Option<Bar> {
296 if bar_index > 5 {
297 return None;
298 }
299 let offset = config::BAR0 + bar_index * 4;
300 let low = self.read_config_u32(offset);
301 if low == 0 {
302 return None;
303 }
304 if (low & 1) != 0 {
305 return Some(Bar::Io {
306 port: (low & 0xFFFF_FFFC) as u16,
307 });
308 }
309 let bar_type = (low >> 1) & 0x3;
310 let prefetchable = ((low >> 3) & 1) != 0;
311 match bar_type {
312 0 => Some(Bar::Memory32 {
313 addr: low & 0xFFFF_FFF0,
314 prefetchable,
315 }),
316 2 => {
317 if bar_index >= 5 {
318 return None;
319 }
320 let high = self.read_config_u32(offset + 4);
321 Some(Bar::Memory64 {
322 addr: ((high as u64) << 32) | ((low & 0xFFFF_FFF0) as u64),
323 prefetchable,
324 })
325 }
326 _ => None,
327 }
328 }
329
330 pub fn read_bar_raw(&self, bar_index: u8) -> Option<u64> {
332 match self.read_bar(bar_index) {
333 Some(Bar::Io { port }) => Some(port as u64),
334 Some(Bar::Memory32 { addr, .. }) => Some(addr as u64),
335 Some(Bar::Memory64 { addr, .. }) => Some(addr),
336 None => None,
337 }
338 }
339
340 pub fn enable_bus_master(&self) {
342 let mut cmd = self.read_config_u16(config::COMMAND);
343 cmd |= command::BUS_MASTER;
344 self.write_config_u16(config::COMMAND, cmd);
345 }
346
347 pub fn enable_memory_space(&self) {
349 let mut cmd = self.read_config_u16(config::COMMAND);
350 cmd |= command::MEMORY_SPACE;
351 self.write_config_u16(config::COMMAND, cmd);
352 }
353
354 pub fn enable_io_space(&self) {
356 let mut cmd = self.read_config_u16(config::COMMAND);
357 cmd |= command::IO_SPACE;
358 self.write_config_u16(config::COMMAND, cmd);
359 }
360}
361
362pub fn all_devices() -> Vec<PciDevice> {
364 let from_bus = all_devices_from_bus_service();
365 if !from_bus.is_empty() {
366 return from_bus;
367 }
368
369 arch_pci::all_devices()
371 .into_iter()
372 .map(|d| PciDevice {
373 address: PciAddress::new(d.address.bus, d.address.device, d.address.function),
374 vendor_id: d.vendor_id,
375 device_id: d.device_id,
376 class_code: d.class_code,
377 subclass: d.subclass,
378 prog_if: d.prog_if,
379 revision: d.revision,
380 header_type: d.header_type,
381 interrupt_line: d.interrupt_line,
382 interrupt_pin: d.interrupt_pin,
383 })
384 .collect()
385}
386
387pub fn find_device(vendor_id: u16, device_id: u16) -> Option<PciDevice> {
389 all_devices()
390 .into_iter()
391 .find(|d| d.vendor_id == vendor_id && d.device_id == device_id)
392}
393
394pub fn find_virtio_device(device_id: u16) -> Option<PciDevice> {
396 find_device(vendor::VIRTIO, device_id)
397}
398
399pub fn find_virtio_devices() -> Vec<PciDevice> {
401 find_devices_by_vendor(vendor::VIRTIO)
402}
403
404pub fn find_devices_by_vendor(vendor_id: u16) -> Vec<PciDevice> {
406 all_devices()
407 .into_iter()
408 .filter(|d| d.vendor_id == vendor_id)
409 .collect()
410}
411
412pub fn find_devices_by_class(class_code: u8, subclass: u8) -> Vec<PciDevice> {
414 all_devices()
415 .into_iter()
416 .filter(|d| d.class_code == class_code && d.subclass == subclass)
417 .collect()
418}
419
420pub fn probe_all(criteria: ProbeCriteria) -> Vec<PciDevice> {
422 all_devices()
423 .into_iter()
424 .filter(|d| criteria.matches(d))
425 .collect()
426}
427
428pub fn probe_first(criteria: ProbeCriteria) -> Option<PciDevice> {
430 all_devices().into_iter().find(|d| criteria.matches(d))
431}
432
433pub fn invalidate_cache() {
435 let _ = open_write("/bus/pci/rescan", &[1, 0, 0, 0]);
436 arch_pci::invalidate_cache();
437}