diff --git a/local/recipes/drivers/linux-kpi/source/src/rust_impl/pci.rs b/local/recipes/drivers/linux-kpi/source/src/rust_impl/pci.rs index cd34ecc2..f04e1c37 100644 --- a/local/recipes/drivers/linux-kpi/source/src/rust_impl/pci.rs +++ b/local/recipes/drivers/linux-kpi/source/src/rust_impl/pci.rs @@ -92,6 +92,27 @@ struct AllocatedVectors { vectors: Vec, } +fn describe_irq_flags(flags: u32) -> String { + let mut parts = Vec::new(); + if flags & PCI_IRQ_MSI != 0 { + parts.push("msi"); + } + if flags & PCI_IRQ_MSIX != 0 { + parts.push("msix"); + } + if flags & PCI_IRQ_LEGACY != 0 { + parts.push("legacy"); + } + if flags & PCI_IRQ_NOLEGACY != 0 { + parts.push("nolegacy"); + } + if parts.is_empty() { + "none".to_string() + } else { + parts.join("|") + } +} + lazy_static::lazy_static! { static ref CURRENT_DEVICE: Mutex> = Mutex::new(None); static ref REGISTERED_PROBE: Mutex> = Mutex::new(None); @@ -294,6 +315,13 @@ fn allocate_vectors(dev: *mut PciDev, min_vecs: i32, max_vecs: i32, flags: u32) } let allocated = (0..count).map(|index| base_irq + index).collect::>(); + log::info!( + "pci_alloc_irq_vectors: base_irq={} count={} flags={} vectors={:?}", + base_irq, + count, + describe_irq_flags(flags), + allocated + ); vectors.insert( dev_key, AllocatedVectors { @@ -309,6 +337,25 @@ pub extern "C" fn pci_enable_device(dev: *mut PciDev) -> i32 { if dev.is_null() { return -EINVAL; } + + #[cfg(target_os = "redox")] + { + let mut pci = match open_current_device(dev) { + Ok(pci) => pci, + Err(error) => return error, + }; + + if let Err(error) = pci.enable_device() { + log::warn!( + "pci_enable_device: failed to enable {:04x}:{:04x}: {}", + unsafe { (*dev).vendor }, + unsafe { (*dev).device }, + error + ); + return -EIO; + } + } + log::info!( "pci_enable_device: vendor=0x{:04x} device=0x{:04x}", unsafe { (*dev).vendor }, @@ -499,6 +546,15 @@ pub extern "C" fn pci_free_irq_vectors(dev: *mut PciDev) { if dev.is_null() { return; } + if let Ok(vectors) = IRQ_VECTORS.lock() { + if let Some(allocated) = vectors.get(&(dev as usize)) { + log::info!( + "pci_free_irq_vectors: releasing {} vectors {:?}", + allocated.vectors.len(), + allocated.vectors + ); + } + } clear_irq_vectors_for_ptr(dev as usize); } @@ -707,4 +763,15 @@ mod tests { -22 ); } + + #[test] + fn describe_irq_flags_formats_requested_modes() { + assert_eq!(describe_irq_flags(0), "none"); + assert_eq!(describe_irq_flags(PCI_IRQ_MSI), "msi"); + assert_eq!(describe_irq_flags(PCI_IRQ_MSIX | PCI_IRQ_NOLEGACY), "msix|nolegacy"); + assert_eq!( + describe_irq_flags(PCI_IRQ_MSI | PCI_IRQ_MSIX | PCI_IRQ_LEGACY), + "msi|msix|legacy" + ); + } } diff --git a/local/recipes/drivers/redox-driver-sys/source/src/irq.rs b/local/recipes/drivers/redox-driver-sys/source/src/irq.rs index 4f9295df..7410b6a4 100644 --- a/local/recipes/drivers/redox-driver-sys/source/src/irq.rs +++ b/local/recipes/drivers/redox-driver-sys/source/src/irq.rs @@ -317,3 +317,91 @@ fn allocate_irq_vector(cpu_id: u8) -> Result<(u32, File)> { "no free IRQ vectors available in {dir}" ))) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::pci::{PciBarInfo, PciBarKind, PciLocation}; + + fn test_device_info() -> PciDeviceInfo { + PciDeviceInfo { + location: PciLocation { + segment: 0, + bus: 0, + device: 0, + function: 0, + }, + vendor_id: 0x1002, + device_id: 0x1234, + subsystem_vendor_id: 0xffff, + subsystem_device_id: 0xffff, + revision: 0, + class_code: 0, + subclass: 0, + prog_if: 0, + header_type: 0, + irq: None, + bars: Vec::new(), + capabilities: Vec::new(), + } + } + + #[test] + fn checked_bar_window_accepts_in_range_region() { + let start = checked_bar_window(0x1000, 0x400, 0x80, 0x40).expect("window in range"); + assert_eq!(start, 0x1080); + } + + #[test] + fn checked_bar_window_rejects_region_past_bar_end() { + let error = checked_bar_window(0x1000, 0x100, 0xf0, 0x20).expect_err("window past end"); + assert!(matches!(error, DriverError::Irq(_))); + } + + #[test] + fn checked_bar_window_rejects_address_overflow() { + let error = checked_bar_window(u64::MAX - 0x10, 0x200, 0x40, 0x20) + .expect_err("overflow should be rejected"); + assert!(matches!(error, DriverError::InvalidParam("MSI-X BAR address overflow"))); + } + + #[test] + fn lookup_msix_bar_requires_memory_bar() { + let mut device = test_device_info(); + device.bars.push(PciBarInfo { + index: 0, + kind: PciBarKind::Io, + addr: 0x3f8, + size: 8, + prefetchable: false, + }); + + let error = lookup_msix_bar(&device, 0, "table").expect_err("I/O BAR should be rejected"); + assert!(matches!(error, DriverError::CapabilityNotFound(_))); + } + + #[test] + fn lookup_msix_bar_returns_matching_memory_bar() { + let mut device = test_device_info(); + device.bars.push(PciBarInfo { + index: 2, + kind: PciBarKind::Memory64, + addr: 0x20_0000, + size: 0x1000, + prefetchable: true, + }); + + let bar = lookup_msix_bar(&device, 2, "table").expect("memory BAR should be found"); + assert_eq!(bar.index, 2); + assert_eq!(bar.addr, 0x20_0000); + } + + #[cfg(not(target_os = "redox"))] + #[test] + fn irq_request_reports_non_redox_platform_limit() { + match IrqHandle::request(5) { + Ok(_) => panic!("host builds should reject IRQ requests"), + Err(error) => assert!(matches!(error, DriverError::Irq(_))), + } + } +} diff --git a/local/recipes/drivers/redox-driver-sys/source/src/pci.rs b/local/recipes/drivers/redox-driver-sys/source/src/pci.rs index 8dd65edd..0cae9cef 100644 --- a/local/recipes/drivers/redox-driver-sys/source/src/pci.rs +++ b/local/recipes/drivers/redox-driver-sys/source/src/pci.rs @@ -90,7 +90,7 @@ impl PciBarInfo { pub fn io_port(&self) -> Option { if self.is_io() && self.addr != 0 { - Some(self.addr as u16) + u16::try_from(self.addr).ok() } else { None } @@ -709,6 +709,9 @@ fn parse_scheme_entry(name: &str) -> Option { } let device = u8::from_str_radix(dev_func[0], 16).ok()?; let function = u8::from_str_radix(dev_func[1], 16).ok()?; + if device > 0x1F || function > 0x07 { + return None; + } Some(PciLocation { segment, bus, @@ -728,3 +731,65 @@ pub fn find_intel_gpus() -> Result> { all.retain(|d| d.is_intel_gpu()); Ok(all) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn pci_location_bdf_round_trip_preserves_bus_device_function() { + let location = PciLocation { + segment: 0, + bus: 0x5a, + device: 0x1c, + function: 0x03, + }; + + let round_trip = PciLocation::from_bdf(location.bdf()); + assert_eq!(round_trip.segment, 0); + assert_eq!(round_trip.bus, location.bus); + assert_eq!(round_trip.device, location.device); + assert_eq!(round_trip.function, location.function); + assert_eq!(location.scheme_path(), "/scheme/pci/0000--5a--1c.3"); + } + + #[test] + fn io_bar_port_requires_nonzero_u16_address() { + let io_bar = PciBarInfo { + index: 0, + kind: PciBarKind::Io, + addr: 0x3f8, + size: 8, + prefetchable: false, + }; + assert_eq!(io_bar.io_port(), Some(0x3f8)); + + let zero_io_bar = PciBarInfo { + addr: 0, + ..io_bar + }; + assert_eq!(zero_io_bar.io_port(), None); + + let oversized_io_bar = PciBarInfo { + addr: u64::from(u16::MAX) + 1, + ..io_bar + }; + assert_eq!(oversized_io_bar.io_port(), None); + } + + #[test] + fn parse_scheme_entry_rejects_invalid_bdf_components() { + assert!(parse_scheme_entry("0000--00--20.0").is_none()); + assert!(parse_scheme_entry("0000--00--1f.8").is_none()); + assert!(parse_scheme_entry("not-a-device").is_none()); + } + + #[test] + fn parse_scheme_entry_accepts_valid_pci_scheme_name() { + let parsed = parse_scheme_entry("0000--80--1f.0").expect("valid PCI entry should parse"); + assert_eq!(parsed.segment, 0); + assert_eq!(parsed.bus, 0x80); + assert_eq!(parsed.device, 0x1f); + assert_eq!(parsed.function, 0x00); + } +}