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}