RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / src / pci.rs
4180 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
//! PCI bus enumeration (the legacy "configuration mechanism #1").
//!
//! Every PCI function has a 256-byte *configuration space*: vendor/device
//! ids, class code, BARs (where its registers live), the command register...
//! On x86 we reach it through two I/O ports: write the address
//! (bus, device, function, register) to 0xCF8, then read/write the data at
//! 0xCFC. Enumerating = asking every possible (bus, device, function) for its
//! vendor id; 0xFFFF means "nobody here".
//!
//! Modern machines also offer memory-mapped config space (PCIe ECAM, found
//! via ACPI's MCFG table); the port method is simpler and QEMU supports both.
//! Reference: https://wiki.osdev.org/PCI

use alloc::vec::Vec;
use x86_64::instructions::port::Port;

const CONFIG_ADDRESS: u16 = 0xCF8;
const CONFIG_DATA: u16 = 0xCFC;

#[derive(Debug, Clone, Copy)]
pub struct PciDevice {
    pub bus: u8,
    pub device: u8,
    pub function: u8,
    pub vendor_id: u16,
    pub device_id: u16,
    pub class: u8,
    pub subclass: u8,
}

/// Read a 32-bit register (`offset` must be 4-byte aligned).
pub fn read_config(bus: u8, device: u8, function: u8, offset: u8) -> u32 {
    let address = 1u32 << 31 // enable bit
        | (bus as u32) << 16
        | (device as u32) << 11
        | (function as u32) << 8
        | (offset as u32 & 0xfc);
    unsafe {
        Port::<u32>::new(CONFIG_ADDRESS).write(address);
        Port::<u32>::new(CONFIG_DATA).read()
    }
}

pub fn write_config(bus: u8, device: u8, function: u8, offset: u8, value: u32) {
    let address = 1u32 << 31 | (bus as u32) << 16 | (device as u32) << 11 | (function as u32) << 8 | (offset as u32 & 0xfc);
    unsafe {
        Port::<u32>::new(CONFIG_ADDRESS).write(address);
        Port::<u32>::new(CONFIG_DATA).write(value);
    }
}

impl PciDevice {
    fn read(&self, offset: u8) -> u32 {
        read_config(self.bus, self.device, self.function, offset)
    }

    /// Base address register `n` (0-5), raw. Bit 0 set = I/O port space.
    pub fn bar(&self, n: u8) -> u32 {
        self.read(0x10 + 4 * n)
    }

    /// Let the device respond to I/O port accesses and do DMA (bus mastering).
    pub fn enable_io_and_bus_master(&self) {
        let command = self.read(0x04);
        write_config(self.bus, self.device, self.function, 0x04, command | 0b101);
    }

    /// Human-readable class (just the common ones).
    pub fn class_name(&self) -> &'static str {
        match (self.class, self.subclass) {
            (0x01, 0x01) => "IDE controller",
            (0x01, 0x00) => "SCSI storage (virtio-blk)",
            (0x01, 0x06) => "SATA controller",
            (0x01, _) => "storage controller",
            (0x02, _) => "network controller",
            (0x03, _) => "display controller",
            (0x06, 0x00) => "host bridge",
            (0x06, 0x01) => "ISA bridge",
            (0x06, 0x80) => "bridge (other)",
            (0x06, _) => "bridge",
            (0x0c, 0x03) => "USB controller",
            _ => "other",
        }
    }
}

fn probe(bus: u8, device: u8, function: u8) -> Option<PciDevice> {
    let id = read_config(bus, device, function, 0x00);
    let vendor_id = id as u16;
    if vendor_id == 0xffff {
        return None; // no such device
    }
    let class = read_config(bus, device, function, 0x08);
    Some(PciDevice {
        bus,
        device,
        function,
        vendor_id,
        device_id: (id >> 16) as u16,
        class: (class >> 24) as u8,
        subclass: (class >> 16) as u8,
    })
}

/// Every PCI function on every bus (brute force: 256 buses x 32 devices --
/// a few thousand port reads, instant in QEMU).
pub fn scan() -> Vec<PciDevice> {
    let mut found = Vec::new();
    for bus in 0..=255u8 {
        for device in 0..32u8 {
            let Some(first) = probe(bus, device, 0) else { continue };
            found.push(first);
            // Header type bit 7: multi-function device (functions 1-7 exist).
            let header_type = (read_config(bus, device, 0, 0x0c) >> 16) as u8;
            if header_type & 0x80 != 0 {
                found.extend((1..8).filter_map(|f| probe(bus, device, f)));
            }
        }
    }
    found
}