kernel/device/pci/config.rs
1//! PCI configuration space access.
2//!
3//! This module provides functions for reading and writing PCI configuration space
4//! using ECAM (Enhanced Configuration Access Mechanism).
5
6use super::PciAddress;
7
8/// Standard PCI configuration space offsets
9pub mod offset {
10 /// Vendor ID (16-bit)
11 pub const VENDOR_ID: usize = 0x00;
12 /// Device ID (16-bit)
13 pub const DEVICE_ID: usize = 0x02;
14 /// Command register (16-bit)
15 pub const COMMAND: usize = 0x04;
16 /// Status register (16-bit)
17 pub const STATUS: usize = 0x06;
18 /// Revision ID (8-bit)
19 pub const REVISION_ID: usize = 0x08;
20 /// Class code (24-bit)
21 pub const CLASS_CODE: usize = 0x09;
22 /// Cache line size (8-bit)
23 pub const CACHE_LINE_SIZE: usize = 0x0C;
24 /// Latency timer (8-bit)
25 pub const LATENCY_TIMER: usize = 0x0D;
26 /// Header type (8-bit)
27 pub const HEADER_TYPE: usize = 0x0E;
28 /// BIST (8-bit)
29 pub const BIST: usize = 0x0F;
30 /// Base Address Register 0 (32-bit)
31 pub const BAR0: usize = 0x10;
32 /// Subsystem Vendor ID (16-bit)
33 pub const SUBSYSTEM_VENDOR_ID: usize = 0x2C;
34 /// Subsystem ID (16-bit)
35 pub const SUBSYSTEM_ID: usize = 0x2E;
36 /// Interrupt Line (8-bit)
37 pub const INTERRUPT_LINE: usize = 0x3C;
38 /// Interrupt Pin (8-bit)
39 pub const INTERRUPT_PIN: usize = 0x3D;
40}
41
42/// PCI configuration space accessor
43///
44/// Provides safe access to PCI configuration space through ECAM mapping.
45pub struct PciConfig {
46 /// Base address of ECAM region
47 ecam_base: usize,
48}
49
50impl PciConfig {
51 /// Create a new PCI configuration accessor
52 ///
53 /// # Arguments
54 ///
55 /// * `ecam_base` - Physical base address of the ECAM region
56 ///
57 /// # Safety
58 ///
59 /// The caller must ensure that the ECAM base address is valid and mapped.
60 pub const fn new(ecam_base: usize) -> Self {
61 Self { ecam_base }
62 }
63
64 /// Calculate the physical address for a configuration register
65 fn config_address(&self, addr: &PciAddress, offset: usize) -> usize {
66 self.ecam_base + addr.ecam_offset() + offset
67 }
68
69 /// Read a 32-bit value from PCI configuration space
70 ///
71 /// # Arguments
72 ///
73 /// * `addr` - PCI device address
74 /// * `offset` - Offset within configuration space (must be 4-byte aligned)
75 ///
76 /// # Returns
77 ///
78 /// The 32-bit value read from configuration space
79 ///
80 /// # Safety
81 ///
82 /// This performs a volatile MMIO read. The caller must ensure the address is valid.
83 pub fn read_u32(&self, addr: &PciAddress, offset: usize) -> u32 {
84 let phys_addr = self.config_address(addr, offset);
85 unsafe { core::ptr::read_volatile(phys_addr as *const u32) }
86 }
87
88 /// Write a 32-bit value to PCI configuration space
89 ///
90 /// # Arguments
91 ///
92 /// * `addr` - PCI device address
93 /// * `offset` - Offset within configuration space (must be 4-byte aligned)
94 /// * `value` - Value to write
95 ///
96 /// # Safety
97 ///
98 /// This performs a volatile MMIO write. The caller must ensure the address is valid.
99 pub fn write_u32(&self, addr: &PciAddress, offset: usize, value: u32) {
100 let phys_addr = self.config_address(addr, offset);
101 unsafe { core::ptr::write_volatile(phys_addr as *mut u32, value) }
102 }
103
104 /// Read a 16-bit value from PCI configuration space
105 ///
106 /// # Arguments
107 ///
108 /// * `addr` - PCI device address
109 /// * `offset` - Offset within configuration space (must be 2-byte aligned)
110 ///
111 /// # Returns
112 ///
113 /// The 16-bit value read from configuration space
114 pub fn read_u16(&self, addr: &PciAddress, offset: usize) -> u16 {
115 let phys_addr = self.config_address(addr, offset);
116 unsafe { core::ptr::read_volatile(phys_addr as *const u16) }
117 }
118
119 /// Write a 16-bit value to PCI configuration space
120 ///
121 /// # Arguments
122 ///
123 /// * `addr` - PCI device address
124 /// * `offset` - Offset within configuration space (must be 2-byte aligned)
125 /// * `value` - Value to write
126 pub fn write_u16(&self, addr: &PciAddress, offset: usize, value: u16) {
127 let phys_addr = self.config_address(addr, offset);
128 unsafe { core::ptr::write_volatile(phys_addr as *mut u16, value) }
129 }
130
131 /// Read an 8-bit value from PCI configuration space
132 ///
133 /// # Arguments
134 ///
135 /// * `addr` - PCI device address
136 /// * `offset` - Offset within configuration space
137 ///
138 /// # Returns
139 ///
140 /// The 8-bit value read from configuration space
141 pub fn read_u8(&self, addr: &PciAddress, offset: usize) -> u8 {
142 let phys_addr = self.config_address(addr, offset);
143 unsafe { core::ptr::read_volatile(phys_addr as *const u8) }
144 }
145
146 /// Write an 8-bit value to PCI configuration space
147 ///
148 /// # Arguments
149 ///
150 /// * `addr` - PCI device address
151 /// * `offset` - Offset within configuration space
152 /// * `value` - Value to write
153 pub fn write_u8(&self, addr: &PciAddress, offset: usize, value: u8) {
154 let phys_addr = self.config_address(addr, offset);
155 unsafe { core::ptr::write_volatile(phys_addr as *mut u8, value) }
156 }
157
158 /// Read vendor ID
159 pub fn read_vendor_id(&self, addr: &PciAddress) -> u16 {
160 self.read_u16(addr, offset::VENDOR_ID)
161 }
162
163 /// Read device ID
164 pub fn read_device_id(&self, addr: &PciAddress) -> u16 {
165 self.read_u16(addr, offset::DEVICE_ID)
166 }
167
168 /// Read class code (24-bit: base class, sub class, interface)
169 pub fn read_class_code(&self, addr: &PciAddress) -> u32 {
170 self.read_u32(addr, offset::CLASS_CODE) >> 8
171 }
172
173 /// Read header type
174 pub fn read_header_type(&self, addr: &PciAddress) -> u8 {
175 self.read_u8(addr, offset::HEADER_TYPE)
176 }
177}
178
179/// PCI vendor IDs (commonly used)
180pub mod vendor {
181 /// Invalid vendor ID (device not present)
182 pub const INVALID: u16 = 0xFFFF;
183 /// Intel Corporation
184 pub const INTEL: u16 = 0x8086;
185 /// AMD
186 pub const AMD: u16 = 0x1022;
187 /// NVIDIA Corporation
188 pub const NVIDIA: u16 = 0x10DE;
189 /// Red Hat, Inc. (QEMU virtio devices)
190 pub const REDHAT: u16 = 0x1AF4;
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test_case]
198 fn test_pci_config_address_calculation() {
199 let config = PciConfig::new(0x3000_0000);
200 let addr = PciAddress::new(0, 0, 0, 0);
201
202 // Test base address
203 assert_eq!(config.config_address(&addr, 0), 0x3000_0000);
204
205 // Test with offset
206 assert_eq!(config.config_address(&addr, 0x10), 0x3000_0010);
207
208 // Test with different bus
209 let addr = PciAddress::new(0, 1, 0, 0);
210 assert_eq!(config.config_address(&addr, 0), 0x3010_0000);
211 }
212}