kernel/device/pci/mod.rs
1//! PCI (Peripheral Component Interconnect) bus module.
2//!
3//! This module provides support for PCI device discovery and management.
4//! It implements PCI configuration space access, device enumeration, and
5//! integration with the device manager.
6//!
7//! # Overview
8//!
9//! PCI is a standard bus for connecting peripheral devices to a computer system.
10//! This implementation focuses on PCIe (PCI Express) using ECAM (Enhanced Configuration
11//! Access Mechanism) which is commonly used on RISC-V and ARM platforms.
12//!
13//! # Architecture
14//!
15//! The PCI subsystem consists of several components:
16//! - **Configuration Space Access**: Reading and writing PCI configuration registers
17//! - **Device Enumeration**: Scanning the PCI bus tree to discover devices
18//! - **Device Information**: Representing PCI device properties (vendor, device ID, etc.)
19//! - **Driver Matching**: Matching PCI devices with appropriate drivers
20//!
21//! # Integration with DeviceManager
22//!
23//! PCI devices are discovered and registered with the DeviceManager, similar to
24//! platform devices. The PCI subsystem provides:
25//! - `PciDeviceInfo`: Device information structure implementing `DeviceInfo` trait
26//! - `PciDeviceDriver`: Driver structure implementing `DeviceDriver` trait
27//!
28//! # Usage
29//!
30//! ```rust,no_run
31//! use crate::device::pci::PciBus;
32//! use crate::device::manager::DeviceManager;
33//!
34//! // Initialize PCI bus with ECAM base address from device tree
35//! let pci_bus = PciBus::new(ecam_base_addr, ecam_size);
36//!
37//! // Scan for devices and register with DeviceManager
38//! pci_bus.scan_and_register();
39//! ```
40//!
41//! # Configuration Space Layout
42//!
43//! PCI configuration space is 256 bytes (4KB for PCIe) per function:
44//! - 0x00-0x3F: Standard PCI configuration header
45//! - 0x40-0xFF: Device-specific configuration
46//! - 0x100-0xFFF: PCIe extended configuration (PCIe only)
47//!
48
49pub mod config;
50pub mod device;
51pub mod driver;
52pub mod scan;
53
54extern crate alloc;
55
56use alloc::vec::Vec;
57use spin::mutex::Mutex;
58
59/// PCI device address components
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub struct PciAddress {
62 /// PCI segment/domain (usually 0)
63 pub segment: u16,
64 /// Bus number (0-255)
65 pub bus: u8,
66 /// Device number (0-31)
67 pub device: u8,
68 /// Function number (0-7)
69 pub function: u8,
70}
71
72impl PciAddress {
73 /// Create a new PCI address
74 pub const fn new(segment: u16, bus: u8, device: u8, function: u8) -> Self {
75 Self {
76 segment,
77 bus,
78 device,
79 function,
80 }
81 }
82
83 /// Calculate ECAM offset for this address
84 ///
85 /// ECAM uses a flat memory mapping where each function gets 4KB:
86 /// offset = (bus << 20) | (device << 15) | (function << 12)
87 pub const fn ecam_offset(&self) -> usize {
88 ((self.bus as usize) << 20)
89 | ((self.device as usize) << 15)
90 | ((self.function as usize) << 12)
91 }
92}
93
94/// PCI bus manager
95///
96/// Manages PCI device discovery and configuration space access.
97pub struct PciBus {
98 /// ECAM (Enhanced Configuration Access Mechanism) base address
99 ecam_base: usize,
100 /// ECAM region size in bytes
101 ecam_size: usize,
102 /// List of discovered PCI devices
103 devices: Mutex<Vec<device::PciDeviceInfo>>,
104}
105
106impl PciBus {
107 /// Create a new PCI bus manager
108 ///
109 /// # Arguments
110 ///
111 /// * `ecam_base` - Physical address of the ECAM region
112 /// * `ecam_size` - Size of the ECAM region in bytes
113 ///
114 /// # Returns
115 ///
116 /// A new `PciBus` instance
117 pub const fn new(ecam_base: usize, ecam_size: usize) -> Self {
118 Self {
119 ecam_base,
120 ecam_size,
121 devices: Mutex::new(Vec::new()),
122 }
123 }
124
125 /// Get the ECAM base address
126 pub const fn ecam_base(&self) -> usize {
127 self.ecam_base
128 }
129
130 /// Get the ECAM size
131 pub const fn ecam_size(&self) -> usize {
132 self.ecam_size
133 }
134
135 /// Check if a PCI address is within the ECAM region
136 pub fn is_valid_address(&self, addr: &PciAddress) -> bool {
137 let offset = addr.ecam_offset();
138 offset + 0x1000 <= self.ecam_size // Each function needs 4KB
139 }
140
141 /// Get the list of discovered devices
142 pub fn devices(&self) -> Vec<device::PciDeviceInfo> {
143 let devices = self.devices.lock();
144 devices.clone()
145 }
146
147 /// Add a device to the list of discovered devices
148 pub fn add_device(&self, device: device::PciDeviceInfo) {
149 let mut devices = self.devices.lock();
150 devices.push(device);
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test_case]
159 fn test_pci_address_ecam_offset() {
160 let addr = PciAddress::new(0, 0, 0, 0);
161 assert_eq!(addr.ecam_offset(), 0);
162
163 let addr = PciAddress::new(0, 1, 0, 0);
164 assert_eq!(addr.ecam_offset(), 1 << 20);
165
166 let addr = PciAddress::new(0, 0, 1, 0);
167 assert_eq!(addr.ecam_offset(), 1 << 15);
168
169 let addr = PciAddress::new(0, 0, 0, 1);
170 assert_eq!(addr.ecam_offset(), 1 << 12);
171
172 let addr = PciAddress::new(0, 1, 2, 3);
173 assert_eq!(addr.ecam_offset(), (1 << 20) | (2 << 15) | (3 << 12));
174 }
175
176 #[test_case]
177 fn test_pci_bus_creation() {
178 let pci_bus = PciBus::new(0x3000_0000, 0x1000_0000);
179 assert_eq!(pci_bus.ecam_base(), 0x3000_0000);
180 assert_eq!(pci_bus.ecam_size(), 0x1000_0000);
181 }
182
183 #[test_case]
184 fn test_pci_address_validity() {
185 let pci_bus = PciBus::new(0x3000_0000, 0x100000); // 1MB ECAM region
186
187 // Valid addresses
188 let addr = PciAddress::new(0, 0, 0, 0);
189 assert!(pci_bus.is_valid_address(&addr));
190
191 // Address at the edge (offset + 4KB <= size)
192 let max_offset = 0x100000 - 0x1000; // 1MB - 4KB
193 let max_bus = (max_offset >> 20) as u8;
194 let addr = PciAddress::new(0, max_bus, 0, 0);
195 assert!(pci_bus.is_valid_address(&addr));
196
197 // Invalid address (beyond ECAM region)
198 let addr = PciAddress::new(0, 255, 31, 7);
199 assert!(!pci_bus.is_valid_address(&addr));
200 }
201
202 #[test_case]
203 fn test_pci_bus_virtio_integration() {
204 use crate::device::DeviceDriver;
205 use crate::device::pci::device::PciDeviceInfo;
206 use crate::device::pci::driver::{PciDeviceDriver, PciDeviceId};
207
208 // Simulate a PCI bus with virtio devices
209 let pci_bus = PciBus::new(0x3000_0000, 0x1000_0000);
210
211 static mut VIRTIO_PROBED_COUNT: usize = 0;
212
213 // Create stub virtio-pci driver
214 let id_table = alloc::vec![
215 PciDeviceId::new(0x1AF4, 0x1000), // VirtIO net
216 PciDeviceId::new(0x1AF4, 0x1001), // VirtIO block
217 ];
218
219 let driver = PciDeviceDriver::new(
220 "virtio-pci-integration",
221 id_table,
222 |device| {
223 unsafe {
224 VIRTIO_PROBED_COUNT += 1;
225 }
226 // Verify this is a virtio device
227 assert_eq!(device.vendor_id(), 0x1AF4);
228 assert!(device.device_id() == 0x1000 || device.device_id() == 0x1001);
229 Ok(())
230 },
231 |_device| Ok(()),
232 );
233
234 // Simulate discovered virtio-net device
235 let addr1 = PciAddress::new(0, 0, 1, 0);
236 let virtio_net = PciDeviceInfo::new(
237 addr1,
238 0x1AF4,
239 0x1000,
240 0x020000,
241 0x00,
242 0x1AF4,
243 0x0001,
244 0x0B,
245 0x01,
246 "virtio_net",
247 1,
248 );
249 pci_bus.add_device(virtio_net.clone());
250
251 // Simulate discovered virtio-blk device
252 let addr2 = PciAddress::new(0, 0, 2, 0);
253 let virtio_blk = PciDeviceInfo::new(
254 addr2,
255 0x1AF4,
256 0x1001,
257 0x010000,
258 0x00,
259 0x1AF4,
260 0x0002,
261 0x0B,
262 0x01,
263 "virtio_blk",
264 2,
265 );
266 pci_bus.add_device(virtio_blk.clone());
267
268 // Verify devices were added
269 let devices = pci_bus.devices();
270 assert_eq!(devices.len(), 2);
271
272 // Probe devices with driver using DeviceDriver trait
273 for device in devices {
274 if driver.matches_device(&device) {
275 // Use the DeviceDriver trait's probe method
276 let result = DeviceDriver::probe(&driver, &device);
277 assert!(result.is_ok());
278 }
279 }
280
281 // Verify both devices were probed
282 assert_eq!(unsafe { VIRTIO_PROBED_COUNT }, 2);
283 }
284}