Skip to main content

strat9_kernel/hardware/
pci_client.rs

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    /// Creates a new instance.
27    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    /// Performs the any operation.
61    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    /// Performs the matches operation.
72    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
92/// Opens read all.
93fn 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
114/// Opens write.
115fn 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
125/// Parses hex u8.
126fn parse_hex_u8(s: &str) -> Option<u8> {
127    u8::from_str_radix(s.trim_start_matches("0x"), 16).ok()
128}
129
130/// Parses hex u16.
131fn parse_hex_u16(s: &str) -> Option<u16> {
132    u16::from_str_radix(s.trim_start_matches("0x"), 16).ok()
133}
134
135/// Parses inventory line.
136fn 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
177/// Performs the all devices from bus service operation.
178fn 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
199/// Performs the cfg path operation.
200fn 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
207/// Converts address to arch address.
208fn to_arch_addr(addr: PciAddress) -> arch_pci::PciAddress {
209    arch_pci::PciAddress::new(addr.bus, addr.device, addr.function)
210}
211
212/// Creates a lightweight arch device handle for direct cfg access.
213fn 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
228/// Performs the cfg read operation.
229fn 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    // Early boot fallback when /bus/pci is not available yet.
237    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
246/// Performs the cfg write operation.
247fn 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    // Early boot fallback when /bus/pci is not available yet.
253    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    /// Reads config u8.
265    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    /// Reads config u16.
270    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    /// Reads config u32.
275    pub fn read_config_u32(&self, offset: u8) -> u32 {
276        cfg_read(self.address, offset, 4).unwrap_or(0)
277    }
278
279    /// Writes config u8.
280    pub fn write_config_u8(&self, offset: u8, value: u8) {
281        let _ = cfg_write(self.address, offset, 1, value as u32);
282    }
283
284    /// Writes config u16.
285    pub fn write_config_u16(&self, offset: u8, value: u16) {
286        let _ = cfg_write(self.address, offset, 2, value as u32);
287    }
288
289    /// Writes config u32.
290    pub fn write_config_u32(&self, offset: u8, value: u32) {
291        let _ = cfg_write(self.address, offset, 4, value);
292    }
293
294    /// Reads bar.
295    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    /// Reads bar raw.
331    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    /// Enables bus master.
341    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    /// Enables memory space.
348    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    /// Enables io space.
355    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
362/// Performs the all devices operation.
363pub 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    // Early boot fallback when /bus/pci/inventory is not ready yet.
370    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
387/// Performs the find device operation.
388pub 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
394/// Performs the find virtio device operation.
395pub fn find_virtio_device(device_id: u16) -> Option<PciDevice> {
396    find_device(vendor::VIRTIO, device_id)
397}
398
399/// Performs the find virtio devices operation.
400pub fn find_virtio_devices() -> Vec<PciDevice> {
401    find_devices_by_vendor(vendor::VIRTIO)
402}
403
404/// Performs the find devices by vendor operation.
405pub 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
412/// Performs the find devices by class operation.
413pub 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
420/// Performs the probe all operation.
421pub fn probe_all(criteria: ProbeCriteria) -> Vec<PciDevice> {
422    all_devices()
423        .into_iter()
424        .filter(|d| criteria.matches(d))
425        .collect()
426}
427
428/// Performs the probe first operation.
429pub fn probe_first(criteria: ProbeCriteria) -> Option<PciDevice> {
430    all_devices().into_iter().find(|d| criteria.matches(d))
431}
432
433/// Performs the invalidate cache operation.
434pub fn invalidate_cache() {
435    let _ = open_write("/bus/pci/rescan", &[1, 0, 0, 0]);
436    arch_pci::invalidate_cache();
437}