RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / src / pci.rs
6696 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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
//! 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).
    /// The legacy interrupt line (8259 IRQ number) the firmware routed this
    /// device's INTx pin to (config register 0x3C, low byte).
    pub fn interrupt_line(&self) -> u8 {
        read_config(self.bus, self.device, self.function, 0x3c) as u8
    }

    pub fn enable_io_and_bus_master(&self) {
        let command = self.read(0x04);
        write_config(self.bus, self.device, self.function, 0x04, command | 0b101);
    }

    /// Same for devices whose registers are memory-mapped (MMIO): command
    /// bit 1 = memory space, bit 2 = bus master.
    pub fn enable_memory_and_bus_master(&self) {
        let command = self.read(0x04);
        write_config(self.bus, self.device, self.function, 0x04, command | 0b110);
    }

    /// Physical address of memory BAR `n`. Bits 1-2 of a memory BAR give its
    /// type: 0b10 = 64-bit, and then BAR n+1 holds the upper half. The low 4
    /// bits are flags, not address.
    pub fn bar_address(&self, n: u8) -> u64 {
        let low = self.bar(n);
        if low & 1 != 0 {
            return (low & !0x3) as u64; // an I/O port BAR
        }
        let mut addr = (low & !0xf) as u64;
        if (low >> 1) & 0b11 == 0b10 {
            addr |= (self.bar(n + 1) as u64) << 32;
        }
        addr
    }

    /// One byte of configuration space (any offset).
    pub fn read_u8(&self, offset: u8) -> u8 {
        (self.read(offset & 0xfc) >> ((offset & 3) * 8)) as u8
    }

    /// Read 32 bits at any 4-byte aligned config offset.
    pub fn read_u32(&self, offset: u8) -> u32 {
        self.read(offset)
    }

    /// The *capability list*: a linked list in config space that newer
    /// devices use to describe extra features (MSI, MSI-X, power
    /// management, and -- what we need -- where modern virtio devices put
    /// their register blocks). Status bit 4 says the list exists; register
    /// 0x34 points at the first entry; each entry is [id, next, data...].
    /// Returns the config-space offsets of the entries with id `cap_id`.
    pub fn capabilities(&self, cap_id: u8) -> Vec<u8> {
        let mut found = Vec::new();
        let status = (self.read(0x04) >> 16) as u16;
        if status & (1 << 4) == 0 {
            return found;
        }
        let mut offset = self.read_u8(0x34) & 0xfc;
        let mut guard = 0; // a broken list must not loop forever
        while offset != 0 && guard < 48 {
            if self.read_u8(offset) == cap_id {
                found.push(offset);
            }
            offset = self.read_u8(offset + 1) & 0xfc;
            guard += 1;
        }
        found
    }

    /// 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",
            (0x00, _) => "unclassified",
            (0x07, _) => "communication 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
}