summaryrefslogtreecommitdiffstats
path: root/rsnix/src/pci.rs
blob: 21d75364485ea958e4c2fd674a2c0e47e469f8a9 (plain) (blame)
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
// ---------------------------------------------------------------------------
// PCI config space — legacy port-based access (0xCF8 / 0xCFC).
//
// Enumeration walks buses 0-255 × devices 0-31 × functions 0-7.  Port I/O
// stays usable after ExitBootServices, so the shell can list devices and
// future drivers (AHCI, network, ...) can find their controllers.
// ---------------------------------------------------------------------------

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

#[derive(Debug, Clone, Copy)]
pub struct DeviceInfo {
    pub bus: u8,
    pub device: u8,
    pub func: u8,
    pub vendor: u16,
    pub device_id: u16,
    pub class: u8,
    pub subclass: u8,
    pub prog_if: u8,
    pub header_type: u8,
}

fn outl(port: u16, val: u32) {
    unsafe {
        core::arch::asm!(
            "out dx, eax",
            in("dx") port,
            in("eax") val,
            options(nomem, nostack, preserves_flags)
        );
    }
}

fn inl(port: u16) -> u32 {
    let val: u32;
    unsafe {
        core::arch::asm!(
            "in eax, dx",
            out("eax") val,
            in("dx") port,
            options(nomem, nostack, preserves_flags)
        );
    }
    val
}

/// Read a dword from a device's config space (`reg` must be dword-aligned).
pub fn config_read(bus: u8, device: u8, func: u8, reg: u8) -> u32 {
    let addr = 0x8000_0000
        | ((bus as u32) << 16)
        | ((device as u32) << 11)
        | ((func as u32) << 8)
        | ((reg as u32) & 0xFC);
    outl(CONFIG_ADDRESS, addr);
    inl(CONFIG_DATA)
}

/// Write a dword to a device's config space (`reg` must be dword-aligned).
pub fn config_write(bus: u8, device: u8, func: u8, reg: u8, val: u32) {
    let addr = 0x8000_0000
        | ((bus as u32) << 16)
        | ((device as u32) << 11)
        | ((func as u32) << 8)
        | ((reg as u32) & 0xFC);
    outl(CONFIG_ADDRESS, addr);
    outl(CONFIG_DATA, val)
}

/// Read a memory BAR (index 0-5) as a 64-bit address, handling 32/64-bit
/// BARs.  Returns None for I/O BARs or invalid values.
pub fn bar64(bus: u8, device: u8, func: u8, bar_index: u8) -> Option<u64> {
    let reg_off = 0x10 + bar_index * 4;
    let lo = config_read(bus, device, func, reg_off);
    if lo & 1 == 1 {
        return None; // I/O BAR
    }
    let kind = (lo >> 1) & 0x3;
    let hi = if kind == 2 {
        config_read(bus, device, func, reg_off + 4)
    } else {
        0
    };
    let base = ((hi as u64) << 32) | (lo as u64 & 0xFFFF_FFF0);
    if base == 0 { None } else { Some(base) }
}

/// Enumerate every device/function present on all buses.
pub fn probe() -> alloc::vec::Vec<DeviceInfo> {
    let mut out = alloc::vec::Vec::new();
    for bus in 0..=255u8 {
        for dev in 0..32u8 {
            for func in 0..8u8 {
                let id = config_read(bus, dev, func, 0);
                let vendor = (id & 0xFFFF) as u16;
                if vendor == 0xFFFF {
                    if func == 0 {
                        break; // nothing on this device number
                    }
                    continue;
                }
                let hdr = config_read(bus, dev, func, 0x0C);
                let header_type = (hdr & 0xFF) as u8;
                let cc = config_read(bus, dev, func, 0x08);
                out.push(DeviceInfo {
                    bus,
                    device: dev,
                    func,
                    vendor,
                    device_id: (id >> 16) as u16,
                    class: (cc >> 24) as u8,
                    subclass: (cc >> 16) as u8,
                    prog_if: (cc >> 8) as u8,
                    header_type,
                });
                // A non-multifunction device has no functions 1-7.
                if func == 0 && header_type & 0x80 == 0 {
                    break;
                }
            }
        }
    }
    out
}

/// Short human name for a (class, subclass) pair.
pub fn class_name(class: u8, subclass: u8) -> &'static str {
    match (class, subclass) {
        (0x00, 0x01) => "VGA-compatible",
        (0x00, _) => "Unclassified",
        (0x01, 0x01) => "IDE controller",
        (0x01, 0x06) => "SATA controller",
        (0x01, 0x08) => "NVM controller",
        (0x01, _) => "Mass storage",
        (0x02, 0x00) => "Ethernet",
        (0x02, _) => "Network",
        (0x03, 0x00) => "VGA",
        (0x03, _) => "Display",
        (0x04, _) => "Multimedia",
        (0x05, _) => "Memory controller",
        (0x06, 0x00) => "Host bridge",
        (0x06, 0x01) => "ISA bridge",
        (0x06, 0x04) => "PCI-PCI bridge",
        (0x06, _) => "Bridge",
        (0x08, 0x80) => "PIC",
        (0x0C, 0x03) => "USB controller",
        (0x0C, 0x05) => "SMBus",
        (0x0C, _) => "Serial bus",
        (0x11, _) => "Signal processing",
        (0xFF, _) => "Unassigned",
        _ => "Other",
    }
}