678980521c
Kernel: - T1.1 msi.rs: MSI message composition (MsiMessage), validation (is_valid_msi_address, is_valid_msi_vector), capability parsing (MsiCapability, MsixCapability) with bounds-safe .get() access - T1.2 vector.rs: per-CPU bitmatrix vector allocation/deallocation - T1.3 IRQ scheme: MSI vector validation gate at irq_trigger, iommu_validate_msi_irq hook, msi_vector_is_valid helper - mod.rs: declarations for msi + vector modules - Fix validation mask 0xFEEF_F000→0xFFF0_0000 (bits 31:20 check) T2.1 driver-sys: program_x86_message kernel-mediated validation - Validates MSI address range 0xFEE0_0000–0xFEEF_EFFF and vector 32–254 - Gated behind #[cfg(target_os = "redox")]; clearly rejects non-Redox - Uses correct 0xFFF0_0000 mask for destination-ID-tolerant validation T2.2 kernel-side affinity: Handle::IrqAffinity variant - kopenat handles <irq>/affinity and cpu-XX/<irq>/affinity paths - kwrite validates CPU id exists, stores mask atomically - kfstat/kfpath/kreadoff/close all handle new variant - Fix unused handle_irq warning in kwrite match arm T2.3 driver-sys: MsiAllocation struct + irq_set_affinity helper - MsiAllocation with round-robin CPU allocation via alloc_cpu_id - irq_set_affinity uses scheme:irq/<irq>/affinity write path - IrqFd type alias in pci.rs for file descriptor tracking IOMMU T3.1: InterruptRemapTable, IRTE encode/decode, IrqRemapManager - IRTE (16-byte) encoding/decoding for AMD-Vi interrupt remapping - InterruptRemapTable with program/invalidate/find_free - IrqRemapManager with multi-table remap and validate_msi gate - Remove arbitrary .min(256) bound on find_free P8-msi.patch: 281-line durable kernel patch, wired in recipe.toml
1059 lines
30 KiB
Rust
1059 lines
30 KiB
Rust
use std::io::{Read, Seek, SeekFrom, Write};
|
|
|
|
use crate::{DriverError, Result};
|
|
|
|
pub const PCI_VENDOR_ID_AMD: u16 = 0x1002;
|
|
pub const PCI_VENDOR_ID_INTEL: u16 = 0x8086;
|
|
pub const PCI_VENDOR_ID_NVIDIA: u16 = 0x10DE;
|
|
|
|
pub const PCI_CLASS_DISPLAY: u8 = 0x03;
|
|
pub const PCI_CLASS_DISPLAY_VGA: u8 = 0x00;
|
|
pub const PCI_CLASS_DISPLAY_3D: u8 = 0x02;
|
|
|
|
pub const PCI_HEADER_TYPE_NORMAL: u8 = 0x00;
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub struct PciLocation {
|
|
pub segment: u16,
|
|
pub bus: u8,
|
|
pub device: u8,
|
|
pub function: u8,
|
|
}
|
|
|
|
impl PciLocation {
|
|
pub fn scheme_path(&self) -> String {
|
|
format!(
|
|
"/scheme/pci/{:04x}--{:02x}--{:02x}.{}",
|
|
self.segment, self.bus, self.device, self.function
|
|
)
|
|
}
|
|
|
|
pub fn bdf(&self) -> u32 {
|
|
((self.bus as u32) << 16)
|
|
| ((self.device as u32) & 0x1F) << 11
|
|
| ((self.function as u32) & 0x07) << 8
|
|
}
|
|
|
|
pub fn from_bdf(bdf: u32) -> Self {
|
|
PciLocation {
|
|
segment: 0,
|
|
bus: ((bdf >> 16) & 0xFF) as u8,
|
|
device: ((bdf >> 11) & 0x1F) as u8,
|
|
function: ((bdf >> 8) & 0x07) as u8,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for PciLocation {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(
|
|
f,
|
|
"{:04x}:{:02x}:{:02x}.{}",
|
|
self.segment, self.bus, self.device, self.function
|
|
)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub struct PciBarInfo {
|
|
pub index: usize,
|
|
pub kind: PciBarKind,
|
|
pub addr: u64,
|
|
pub size: u64,
|
|
pub prefetchable: bool,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum PciBarKind {
|
|
Memory32,
|
|
Memory64,
|
|
Io,
|
|
None,
|
|
}
|
|
|
|
impl PciBarInfo {
|
|
pub fn is_memory(&self) -> bool {
|
|
matches!(self.kind, PciBarKind::Memory32 | PciBarKind::Memory64)
|
|
}
|
|
|
|
pub fn is_io(&self) -> bool {
|
|
self.kind == PciBarKind::Io
|
|
}
|
|
|
|
pub fn memory_info(&self) -> Option<(u64, usize)> {
|
|
if self.is_memory() && self.addr != 0 && self.size != 0 {
|
|
Some((self.addr, self.size as usize))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn io_port(&self) -> Option<u16> {
|
|
if self.is_io() && self.addr != 0 {
|
|
u16::try_from(self.addr).ok()
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
pub const PCI_CMD_IO_SPACE: u16 = 0x0001;
|
|
pub const PCI_CMD_MEMORY_SPACE: u16 = 0x0002;
|
|
pub const PCI_CMD_BUS_MASTER: u16 = 0x0004;
|
|
pub const PCI_CMD_MEM_WRITE_INVALIDATE: u16 = 0x0010;
|
|
pub const PCI_CMD_PARITY_ERROR_RESPONSE: u16 = 0x0040;
|
|
pub const PCI_CMD_SERR_ENABLE: u16 = 0x0100;
|
|
pub const PCI_CMD_INTX_DISABLE: u16 = 0x0400;
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct PciCapability {
|
|
pub id: u8,
|
|
pub offset: u8,
|
|
pub vendor_cap_id: Option<u8>,
|
|
}
|
|
|
|
pub const PCI_CAP_ID_MSI: u8 = 0x05;
|
|
pub const PCI_CAP_ID_ATS: u8 = 0x0B;
|
|
pub const PCI_CAP_ID_MSIX: u8 = 0x11;
|
|
pub const PCI_CAP_ID_PCIE: u8 = 0x10;
|
|
pub const PCI_CAP_ID_POWER: u8 = 0x01;
|
|
pub const PCI_CAP_ID_VNDR: u8 = 0x09;
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum InterruptSupport {
|
|
None,
|
|
LegacyOnly,
|
|
Msi,
|
|
MsiX,
|
|
}
|
|
|
|
impl InterruptSupport {
|
|
pub fn as_str(self) -> &'static str {
|
|
match self {
|
|
Self::None => "none",
|
|
Self::LegacyOnly => "legacy",
|
|
Self::Msi => "msi",
|
|
Self::MsiX => "msix",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct MsixCapability {
|
|
pub table_bar: u8,
|
|
pub table_offset: u32,
|
|
pub pba_bar: u8,
|
|
pub pba_offset: u32,
|
|
pub table_size: u16,
|
|
pub masked: bool,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct PciDeviceInfo {
|
|
pub location: PciLocation,
|
|
pub vendor_id: u16,
|
|
pub device_id: u16,
|
|
pub subsystem_vendor_id: u16,
|
|
pub subsystem_device_id: u16,
|
|
pub revision: u8,
|
|
pub class_code: u8,
|
|
pub subclass: u8,
|
|
pub prog_if: u8,
|
|
pub header_type: u8,
|
|
pub irq: Option<u32>,
|
|
pub bars: Vec<PciBarInfo>,
|
|
pub capabilities: Vec<PciCapability>,
|
|
}
|
|
|
|
pub type IrqFd = std::fs::File;
|
|
|
|
impl PciDeviceInfo {
|
|
pub fn is_gpu(&self) -> bool {
|
|
self.class_code == PCI_CLASS_DISPLAY
|
|
}
|
|
|
|
pub fn is_amd_gpu(&self) -> bool {
|
|
self.class_code == PCI_CLASS_DISPLAY && self.vendor_id == PCI_VENDOR_ID_AMD
|
|
}
|
|
|
|
pub fn is_intel_gpu(&self) -> bool {
|
|
self.class_code == PCI_CLASS_DISPLAY && self.vendor_id == PCI_VENDOR_ID_INTEL
|
|
}
|
|
|
|
pub fn find_capability(&self, id: u8) -> Option<&PciCapability> {
|
|
self.capabilities.iter().find(|c| c.id == id)
|
|
}
|
|
|
|
pub fn find_msix(&self) -> Option<MsixCapability> {
|
|
self.find_capability(PCI_CAP_ID_MSIX).and_then(|cap| {
|
|
let mut dev = PciDevice::from_info(self).ok()?;
|
|
dev.parse_msix(cap.offset).ok()
|
|
})
|
|
}
|
|
|
|
pub fn find_memory_bar(&self, index: usize) -> Option<&PciBarInfo> {
|
|
self.bars.iter().find(|b| b.index == index && b.is_memory())
|
|
}
|
|
|
|
pub fn supports_msi(&self) -> bool {
|
|
self.find_capability(PCI_CAP_ID_MSI).is_some()
|
|
}
|
|
|
|
pub fn supports_msix(&self) -> bool {
|
|
self.find_capability(PCI_CAP_ID_MSIX).is_some()
|
|
}
|
|
|
|
pub fn interrupt_support(&self) -> InterruptSupport {
|
|
let quirks = self.quirks();
|
|
|
|
let has_legacy = self.irq.is_some();
|
|
let has_msi = self.supports_msi() && !quirks.contains(crate::quirks::PciQuirkFlags::NO_MSI);
|
|
let has_msix =
|
|
self.supports_msix() && !quirks.contains(crate::quirks::PciQuirkFlags::NO_MSIX);
|
|
|
|
if quirks.contains(crate::quirks::PciQuirkFlags::FORCE_LEGACY_IRQ) {
|
|
return if has_legacy {
|
|
InterruptSupport::LegacyOnly
|
|
} else {
|
|
InterruptSupport::None
|
|
};
|
|
}
|
|
|
|
if has_msix {
|
|
InterruptSupport::MsiX
|
|
} else if has_msi {
|
|
InterruptSupport::Msi
|
|
} else if has_legacy {
|
|
InterruptSupport::LegacyOnly
|
|
} else {
|
|
InterruptSupport::None
|
|
}
|
|
}
|
|
|
|
pub fn quirks(&self) -> crate::quirks::PciQuirkFlags {
|
|
crate::quirks::lookup_pci_quirks(self)
|
|
}
|
|
|
|
pub fn has_quirk(&self, flag: crate::quirks::PciQuirkFlags) -> bool {
|
|
self.quirks().contains(flag)
|
|
}
|
|
}
|
|
|
|
pub struct PciDevice {
|
|
location: PciLocation,
|
|
config_fd: std::fs::File,
|
|
}
|
|
|
|
impl PciDevice {
|
|
pub fn open(segment: u16, bus: u8, device: u8, function: u8) -> Result<Self> {
|
|
let loc = PciLocation {
|
|
segment,
|
|
bus,
|
|
device,
|
|
function,
|
|
};
|
|
Self::open_location(&loc)
|
|
}
|
|
|
|
pub fn open_location(loc: &PciLocation) -> Result<Self> {
|
|
let config_path = format!("{}/config", loc.scheme_path());
|
|
let fd = std::fs::OpenOptions::new()
|
|
.read(true)
|
|
.write(true)
|
|
.open(&config_path)
|
|
.map_err(|e| {
|
|
DriverError::Pci(format!("cannot open PCI config at {}: {}", config_path, e))
|
|
})?;
|
|
Ok(PciDevice {
|
|
location: *loc,
|
|
config_fd: fd,
|
|
})
|
|
}
|
|
|
|
pub fn from_info(info: &PciDeviceInfo) -> Result<Self> {
|
|
Self::open_location(&info.location)
|
|
}
|
|
|
|
pub fn location(&self) -> &PciLocation {
|
|
&self.location
|
|
}
|
|
|
|
pub fn read_config_dword(&mut self, offset: u64) -> Result<u32> {
|
|
self.config_fd.seek(SeekFrom::Start(offset))?;
|
|
let mut buf = [0u8; 4];
|
|
self.config_fd.read_exact(&mut buf)?;
|
|
Ok(u32::from_le_bytes(buf))
|
|
}
|
|
|
|
pub fn read_config_word(&mut self, offset: u64) -> Result<u16> {
|
|
self.config_fd.seek(SeekFrom::Start(offset))?;
|
|
let mut buf = [0u8; 2];
|
|
self.config_fd.read_exact(&mut buf)?;
|
|
Ok(u16::from_le_bytes(buf))
|
|
}
|
|
|
|
pub fn read_config_byte(&mut self, offset: u64) -> Result<u8> {
|
|
self.config_fd.seek(SeekFrom::Start(offset))?;
|
|
let mut buf = [0u8; 1];
|
|
self.config_fd.read_exact(&mut buf)?;
|
|
Ok(buf[0])
|
|
}
|
|
|
|
pub fn write_config_dword(&mut self, offset: u64, val: u32) -> Result<()> {
|
|
self.config_fd.seek(SeekFrom::Start(offset))?;
|
|
self.config_fd.write_all(&val.to_le_bytes())?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn write_config_word(&mut self, offset: u64, val: u16) -> Result<()> {
|
|
self.config_fd.seek(SeekFrom::Start(offset))?;
|
|
self.config_fd.write_all(&val.to_le_bytes())?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn write_config_byte(&mut self, offset: u64, val: u8) -> Result<()> {
|
|
self.config_fd.seek(SeekFrom::Start(offset))?;
|
|
self.config_fd.write_all(&[val])?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn vendor_id(&mut self) -> Result<u16> {
|
|
self.read_config_word(0x00)
|
|
}
|
|
|
|
pub fn device_id(&mut self) -> Result<u16> {
|
|
self.read_config_word(0x02)
|
|
}
|
|
|
|
pub fn command(&mut self) -> Result<u16> {
|
|
self.read_config_word(0x04)
|
|
}
|
|
|
|
pub fn set_command(&mut self, flags: u16) -> Result<()> {
|
|
self.write_config_word(0x04, flags)
|
|
}
|
|
|
|
pub fn enable_device(&mut self) -> Result<()> {
|
|
let mut cmd = self.command()?;
|
|
cmd |= PCI_CMD_IO_SPACE | PCI_CMD_MEMORY_SPACE | PCI_CMD_BUS_MASTER;
|
|
self.set_command(cmd)
|
|
}
|
|
|
|
pub fn set_bus_master(&mut self, enable: bool) -> Result<()> {
|
|
let mut cmd = self.command()?;
|
|
if enable {
|
|
cmd |= 0x0004;
|
|
} else {
|
|
cmd &= !0x0004;
|
|
}
|
|
self.set_command(cmd)
|
|
}
|
|
|
|
pub fn set_intx_disable(&mut self, disable: bool) -> Result<()> {
|
|
let mut cmd = self.command()?;
|
|
if disable {
|
|
cmd |= 0x0400;
|
|
} else {
|
|
cmd &= !0x0400;
|
|
}
|
|
self.set_command(cmd)
|
|
}
|
|
|
|
pub fn status(&mut self) -> Result<u16> {
|
|
self.read_config_word(0x06)
|
|
}
|
|
|
|
pub fn revision(&mut self) -> Result<u8> {
|
|
self.read_config_byte(0x08)
|
|
}
|
|
|
|
pub fn class_code(&mut self) -> Result<u8> {
|
|
self.read_config_byte(0x0B)
|
|
}
|
|
|
|
pub fn subclass(&mut self) -> Result<u8> {
|
|
self.read_config_byte(0x0A)
|
|
}
|
|
|
|
pub fn prog_if(&mut self) -> Result<u8> {
|
|
self.read_config_byte(0x09)
|
|
}
|
|
|
|
pub fn header_type(&mut self) -> Result<u8> {
|
|
let ht = self.read_config_byte(0x0E)?;
|
|
Ok(ht & 0x7F)
|
|
}
|
|
|
|
pub fn is_multi_function(&mut self) -> Result<bool> {
|
|
let ht = self.read_config_byte(0x0E)?;
|
|
Ok(ht & 0x80 != 0)
|
|
}
|
|
|
|
pub fn irq_line(&mut self) -> Result<u8> {
|
|
self.read_config_byte(0x3C)
|
|
}
|
|
|
|
pub fn irq_pin(&mut self) -> Result<u8> {
|
|
self.read_config_byte(0x3D)
|
|
}
|
|
|
|
pub fn full_info(&mut self) -> Result<PciDeviceInfo> {
|
|
let vendor_id = self.vendor_id()?;
|
|
let device_id = self.device_id()?;
|
|
let revision = self.revision()?;
|
|
let prog_if = self.prog_if()?;
|
|
let subclass = self.subclass()?;
|
|
let class_code = self.class_code()?;
|
|
let header_type = self.header_type()?;
|
|
let irq_byte = self.irq_line()?;
|
|
let bars = if header_type == PCI_HEADER_TYPE_NORMAL {
|
|
self.parse_bars()?
|
|
} else {
|
|
Vec::new()
|
|
};
|
|
let capabilities = if header_type == PCI_HEADER_TYPE_NORMAL {
|
|
self.parse_capabilities()?
|
|
} else {
|
|
Vec::new()
|
|
};
|
|
|
|
let (subsystem_vendor_id, subsystem_device_id) = if header_type == PCI_HEADER_TYPE_NORMAL {
|
|
(
|
|
self.read_config_word(0x2C).unwrap_or(0xFFFF),
|
|
self.read_config_word(0x2E).unwrap_or(0xFFFF),
|
|
)
|
|
} else {
|
|
(0xFFFF, 0xFFFF)
|
|
};
|
|
|
|
Ok(PciDeviceInfo {
|
|
location: self.location,
|
|
vendor_id,
|
|
device_id,
|
|
subsystem_vendor_id,
|
|
subsystem_device_id,
|
|
revision,
|
|
class_code,
|
|
subclass,
|
|
prog_if,
|
|
header_type,
|
|
irq: if irq_byte != 0 && irq_byte != 0xFF {
|
|
Some(irq_byte as u32)
|
|
} else {
|
|
None
|
|
},
|
|
bars,
|
|
capabilities,
|
|
})
|
|
}
|
|
|
|
pub fn parse_bars(&mut self) -> Result<Vec<PciBarInfo>> {
|
|
let mut bars = Vec::with_capacity(6);
|
|
let mut bar_idx = 0usize;
|
|
let mut config_offset = 0x10u64;
|
|
|
|
while bar_idx < 6 && config_offset <= 0x24 {
|
|
let val_lo = self.read_config_dword(config_offset)?;
|
|
|
|
if val_lo == 0 {
|
|
bars.push(PciBarInfo {
|
|
index: bar_idx,
|
|
kind: PciBarKind::None,
|
|
addr: 0,
|
|
size: 0,
|
|
prefetchable: false,
|
|
});
|
|
bar_idx += 1;
|
|
config_offset += 4;
|
|
continue;
|
|
}
|
|
|
|
let is_io = (val_lo & 0x01) != 0;
|
|
|
|
if is_io {
|
|
let addr = (val_lo & 0xFFFFFFFC) as u64;
|
|
let size = self.probe_bar_size(config_offset)?;
|
|
bars.push(PciBarInfo {
|
|
index: bar_idx,
|
|
kind: PciBarKind::Io,
|
|
addr,
|
|
size,
|
|
prefetchable: false,
|
|
});
|
|
bar_idx += 1;
|
|
config_offset += 4;
|
|
} else {
|
|
let is_64bit = ((val_lo >> 2) & 0x01) != 0;
|
|
let prefetchable = ((val_lo >> 3) & 0x01) != 0;
|
|
|
|
let addr_lo = (val_lo & 0xFFFFFFF0) as u64;
|
|
let (addr, size) = if is_64bit {
|
|
let val_hi = self.read_config_dword(config_offset + 4)?;
|
|
let full_addr = addr_lo | ((val_hi as u64) << 32);
|
|
let full_size = self.probe_bar64_size(config_offset)?;
|
|
bars.push(PciBarInfo {
|
|
index: bar_idx,
|
|
kind: PciBarKind::Memory64,
|
|
addr: full_addr,
|
|
size: full_size,
|
|
prefetchable,
|
|
});
|
|
bar_idx += 2;
|
|
config_offset += 8;
|
|
continue;
|
|
} else {
|
|
let sz = self.probe_bar_size(config_offset)?;
|
|
(addr_lo, sz)
|
|
};
|
|
|
|
bars.push(PciBarInfo {
|
|
index: bar_idx,
|
|
kind: PciBarKind::Memory32,
|
|
addr,
|
|
size,
|
|
prefetchable,
|
|
});
|
|
bar_idx += 1;
|
|
config_offset += 4;
|
|
}
|
|
}
|
|
|
|
Ok(bars)
|
|
}
|
|
|
|
fn probe_bar_size(&mut self, offset: u64) -> Result<u64> {
|
|
let original = self.read_config_dword(offset)?;
|
|
self.write_config_dword(offset, 0xFFFFFFFF)?;
|
|
let inverted = self.read_config_dword(offset)?;
|
|
self.write_config_dword(offset, original)?;
|
|
|
|
let is_io = (original & 0x01) != 0;
|
|
let mask = if is_io { 0xFFFFFFFC } else { 0xFFFFFFF0 };
|
|
|
|
let size_val = !(inverted & mask) & mask;
|
|
if size_val == 0 {
|
|
return Ok(0);
|
|
}
|
|
Ok(size_val as u64)
|
|
}
|
|
|
|
fn probe_bar64_size(&mut self, offset: u64) -> Result<u64> {
|
|
let original_lo = self.read_config_dword(offset)?;
|
|
let original_hi = self.read_config_dword(offset + 4)?;
|
|
|
|
self.write_config_dword(offset, 0xFFFFFFFF)?;
|
|
self.write_config_dword(offset + 4, 0xFFFFFFFF)?;
|
|
|
|
let inverted_lo = self.read_config_dword(offset)?;
|
|
let inverted_hi = self.read_config_dword(offset + 4)?;
|
|
|
|
self.write_config_dword(offset, original_lo)?;
|
|
self.write_config_dword(offset + 4, original_hi)?;
|
|
|
|
let lo = !(inverted_lo & 0xFFFFFFF0) & 0xFFFFFFF0;
|
|
let hi = !inverted_hi;
|
|
|
|
if lo == 0 && hi == 0 {
|
|
return Ok(0);
|
|
}
|
|
|
|
let size = ((hi as u64) << 32) | (lo as u64);
|
|
Ok(size)
|
|
}
|
|
|
|
pub fn parse_capabilities(&mut self) -> Result<Vec<PciCapability>> {
|
|
let status = self.status()?;
|
|
if status & 0x0010 == 0 {
|
|
return Ok(Vec::new());
|
|
}
|
|
|
|
let mut caps = Vec::new();
|
|
let mut cap_ptr = self.read_config_byte(0x34)? as u64;
|
|
|
|
let mut visited = 0u8;
|
|
while cap_ptr >= 0x40 && visited < 48 {
|
|
let cap_id = self.read_config_byte(cap_ptr)?;
|
|
let next_ptr = self.read_config_byte(cap_ptr + 1)? as u64;
|
|
|
|
if cap_id == 0 {
|
|
break;
|
|
}
|
|
|
|
let vendor_cap_id = if cap_id == PCI_CAP_ID_VNDR {
|
|
self.read_config_byte(cap_ptr + 2).ok()
|
|
} else {
|
|
None
|
|
};
|
|
|
|
caps.push(PciCapability {
|
|
id: cap_id,
|
|
offset: cap_ptr as u8,
|
|
vendor_cap_id,
|
|
});
|
|
|
|
if next_ptr == 0 || next_ptr <= cap_ptr {
|
|
break;
|
|
}
|
|
cap_ptr = next_ptr;
|
|
visited += 1;
|
|
}
|
|
|
|
Ok(caps)
|
|
}
|
|
|
|
pub fn parse_msix(&mut self, cap_offset: u8) -> Result<MsixCapability> {
|
|
let msg_ctrl = self.read_config_word(cap_offset as u64 + 2)?;
|
|
let table_raw = self.read_config_dword(cap_offset as u64 + 4)?;
|
|
let pba_raw = self.read_config_dword(cap_offset as u64 + 8)?;
|
|
|
|
let table_bar = (table_raw & 0x07) as u8;
|
|
let table_offset = table_raw & 0xFFFFFFF8;
|
|
let pba_bar = (pba_raw & 0x07) as u8;
|
|
let pba_offset = pba_raw & 0xFFFFFFF8;
|
|
let table_size = (msg_ctrl & 0x07FF) + 1;
|
|
let masked = (msg_ctrl & 0x8000) != 0;
|
|
|
|
Ok(MsixCapability {
|
|
table_bar,
|
|
table_offset,
|
|
pba_bar,
|
|
pba_offset,
|
|
table_size,
|
|
masked,
|
|
})
|
|
}
|
|
|
|
/// Enable MSI-X by setting the MSI-X Enable bit (bit 14) in Message Control.
|
|
/// Per PCI spec §6.8.2, bit 14 enables MSI-X; bit 15 is Function Mask.
|
|
pub fn enable_msix(&mut self, cap_offset: u8) -> Result<()> {
|
|
let msg_ctrl = self.read_config_word(cap_offset as u64 + 2)?;
|
|
let new_ctrl = msg_ctrl | 0x4000;
|
|
self.write_config_word(cap_offset as u64 + 2, new_ctrl)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Disable MSI-X by clearing the MSI-X Enable bit (bit 14) in Message Control.
|
|
pub fn disable_msix(&mut self, cap_offset: u8) -> Result<()> {
|
|
let msg_ctrl = self.read_config_word(cap_offset as u64 + 2)?;
|
|
let new_ctrl = msg_ctrl & !0x4000;
|
|
self.write_config_word(cap_offset as u64 + 2, new_ctrl)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn map_bar(
|
|
&mut self,
|
|
_bar_index: usize,
|
|
phys_addr: u64,
|
|
size: usize,
|
|
) -> Result<crate::memory::MmioRegion> {
|
|
crate::memory::MmioRegion::map(
|
|
phys_addr,
|
|
size,
|
|
crate::memory::CacheType::DeviceMemory,
|
|
crate::memory::MmioProt::READ_WRITE,
|
|
)
|
|
}
|
|
}
|
|
|
|
impl std::io::Write for PciDevice {
|
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
|
self.config_fd.write(buf)
|
|
}
|
|
|
|
fn flush(&mut self) -> std::io::Result<()> {
|
|
self.config_fd.flush()
|
|
}
|
|
}
|
|
|
|
fn enumerate_pci_filtered(class: Option<u8>) -> Result<Vec<PciDeviceInfo>> {
|
|
let entries = std::fs::read_dir("/scheme/pci")?;
|
|
let mut devices = Vec::new();
|
|
|
|
for entry in entries {
|
|
let entry = entry?;
|
|
let name = entry.file_name();
|
|
let name_str = match name.to_str() {
|
|
Some(s) => s,
|
|
None => continue,
|
|
};
|
|
|
|
// pcid scheme entries use format: segment--bus--device.function
|
|
let location = match parse_scheme_entry(name_str) {
|
|
Some(loc) => loc,
|
|
None => continue,
|
|
};
|
|
|
|
let config_path = format!("{}/config", location.scheme_path());
|
|
if let Ok(data) = std::fs::read(&config_path) {
|
|
if let Some(info) = parse_device_info_from_config_space(location, &data)
|
|
.filter(|info| class.is_none_or(|class| info.class_code == class))
|
|
{
|
|
devices.push(info);
|
|
}
|
|
}
|
|
}
|
|
|
|
log::debug!(
|
|
"PCI enumeration{}: found {} devices",
|
|
class
|
|
.map(|class| format!(" for class {class:#04x}"))
|
|
.unwrap_or_default(),
|
|
devices.len()
|
|
);
|
|
Ok(devices)
|
|
}
|
|
|
|
pub fn parse_device_info_from_config_space(location: PciLocation, data: &[u8]) -> Option<PciDeviceInfo> {
|
|
if data.len() < 64 {
|
|
return None;
|
|
}
|
|
|
|
let class_code = data[0x0b];
|
|
|
|
let header_type = data[0x0e] & 0x7F;
|
|
let capabilities = if header_type == PCI_HEADER_TYPE_NORMAL {
|
|
parse_capabilities_from_config_bytes(data)
|
|
} else {
|
|
Vec::new()
|
|
};
|
|
|
|
let (subsystem_vendor_id, subsystem_device_id) =
|
|
if header_type == PCI_HEADER_TYPE_NORMAL && data.len() > 0x2F {
|
|
(
|
|
u16::from_le_bytes([data[0x2c], data[0x2d]]),
|
|
u16::from_le_bytes([data[0x2e], data[0x2f]]),
|
|
)
|
|
} else {
|
|
(0xFFFF, 0xFFFF)
|
|
};
|
|
|
|
let irq_line = data[0x3c];
|
|
Some(PciDeviceInfo {
|
|
location,
|
|
vendor_id: u16::from_le_bytes([data[0x00], data[0x01]]),
|
|
device_id: u16::from_le_bytes([data[0x02], data[0x03]]),
|
|
subsystem_vendor_id,
|
|
subsystem_device_id,
|
|
revision: data[0x08],
|
|
class_code,
|
|
subclass: data[0x0a],
|
|
prog_if: data[0x09],
|
|
header_type,
|
|
irq: if irq_line != 0 && irq_line != 0xff {
|
|
Some(irq_line as u32)
|
|
} else {
|
|
None
|
|
},
|
|
bars: Vec::new(),
|
|
capabilities,
|
|
})
|
|
}
|
|
|
|
fn parse_capabilities_from_config_bytes(data: &[u8]) -> Vec<PciCapability> {
|
|
if data.len() < 64 {
|
|
return Vec::new();
|
|
}
|
|
|
|
let status = u16::from_le_bytes([data[0x06], data[0x07]]);
|
|
if status & 0x0010 == 0 {
|
|
return Vec::new();
|
|
}
|
|
|
|
let mut caps = Vec::new();
|
|
let mut cap_ptr = usize::from(data[0x34]);
|
|
let mut visited = 0u8;
|
|
|
|
while cap_ptr >= 0x40 && cap_ptr + 1 < data.len() && visited < 48 {
|
|
let cap_id = data[cap_ptr];
|
|
let next_ptr = usize::from(data[cap_ptr + 1]);
|
|
|
|
if cap_id == 0 {
|
|
break;
|
|
}
|
|
|
|
let vendor_cap_id = if cap_id == PCI_CAP_ID_VNDR && cap_ptr + 2 < data.len() {
|
|
Some(data[cap_ptr + 2])
|
|
} else {
|
|
None
|
|
};
|
|
|
|
caps.push(PciCapability {
|
|
id: cap_id,
|
|
offset: cap_ptr as u8,
|
|
vendor_cap_id,
|
|
});
|
|
|
|
if next_ptr == 0 || next_ptr <= cap_ptr {
|
|
break;
|
|
}
|
|
|
|
cap_ptr = next_ptr;
|
|
visited += 1;
|
|
}
|
|
|
|
caps
|
|
}
|
|
|
|
pub fn enumerate_pci_class(class: u8) -> Result<Vec<PciDeviceInfo>> {
|
|
enumerate_pci_filtered(Some(class))
|
|
}
|
|
|
|
pub fn enumerate_pci_all() -> Result<Vec<PciDeviceInfo>> {
|
|
enumerate_pci_filtered(None)
|
|
}
|
|
|
|
fn parse_scheme_entry(name: &str) -> Option<PciLocation> {
|
|
let parts: Vec<&str> = name.splitn(3, "--").collect();
|
|
if parts.len() != 3 {
|
|
return None;
|
|
}
|
|
let segment = u16::from_str_radix(parts[0], 16).ok()?;
|
|
let bus = u8::from_str_radix(parts[1], 16).ok()?;
|
|
let dev_func: Vec<&str> = parts[2].splitn(2, '.').collect();
|
|
if dev_func.len() != 2 {
|
|
return None;
|
|
}
|
|
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,
|
|
device,
|
|
function,
|
|
})
|
|
}
|
|
|
|
pub fn find_amd_gpus() -> Result<Vec<PciDeviceInfo>> {
|
|
let mut all = enumerate_pci_class(PCI_CLASS_DISPLAY)?;
|
|
all.retain(|d| d.is_amd_gpu());
|
|
Ok(all)
|
|
}
|
|
|
|
pub fn find_intel_gpus() -> Result<Vec<PciDeviceInfo>> {
|
|
let mut all = enumerate_pci_class(PCI_CLASS_DISPLAY)?;
|
|
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);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_capabilities_from_config_bytes_reads_standard_and_vendor_caps() {
|
|
let mut data = vec![0u8; 256];
|
|
data[0x06] = 0x10;
|
|
data[0x34] = 0x50;
|
|
|
|
data[0x50] = PCI_CAP_ID_MSI;
|
|
data[0x51] = 0x60;
|
|
|
|
data[0x60] = PCI_CAP_ID_VNDR;
|
|
data[0x61] = 0x00;
|
|
data[0x62] = 0xAB;
|
|
|
|
let caps = parse_capabilities_from_config_bytes(&data);
|
|
assert_eq!(caps.len(), 2);
|
|
assert_eq!(caps[0].id, PCI_CAP_ID_MSI);
|
|
assert_eq!(caps[0].offset, 0x50);
|
|
assert_eq!(caps[0].vendor_cap_id, None);
|
|
assert_eq!(caps[1].id, PCI_CAP_ID_VNDR);
|
|
assert_eq!(caps[1].offset, 0x60);
|
|
assert_eq!(caps[1].vendor_cap_id, Some(0xAB));
|
|
}
|
|
|
|
#[test]
|
|
fn parse_capabilities_from_config_bytes_stops_on_backwards_pointer() {
|
|
let mut data = vec![0u8; 256];
|
|
data[0x06] = 0x10;
|
|
data[0x34] = 0x50;
|
|
data[0x50] = PCI_CAP_ID_MSI;
|
|
data[0x51] = 0x48;
|
|
|
|
let caps = parse_capabilities_from_config_bytes(&data);
|
|
assert_eq!(caps.len(), 1);
|
|
assert_eq!(caps[0].id, PCI_CAP_ID_MSI);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_device_info_from_config_bytes_includes_capabilities() {
|
|
let mut data = vec![0u8; 256];
|
|
data[0x00] = 0x86;
|
|
data[0x01] = 0x80;
|
|
data[0x02] = 0x34;
|
|
data[0x03] = 0x12;
|
|
data[0x06] = 0x10;
|
|
data[0x08] = 0x02;
|
|
data[0x09] = 0x01;
|
|
data[0x0a] = PCI_CLASS_DISPLAY_VGA;
|
|
data[0x0b] = PCI_CLASS_DISPLAY;
|
|
data[0x0e] = PCI_HEADER_TYPE_NORMAL;
|
|
data[0x2c] = 0x86;
|
|
data[0x2d] = 0x80;
|
|
data[0x2e] = 0x78;
|
|
data[0x2f] = 0x56;
|
|
data[0x34] = 0x50;
|
|
data[0x3c] = 11;
|
|
data[0x50] = PCI_CAP_ID_MSIX;
|
|
data[0x51] = 0x00;
|
|
|
|
let info = parse_device_info_from_config_space(
|
|
PciLocation {
|
|
segment: 0,
|
|
bus: 0,
|
|
device: 2,
|
|
function: 0,
|
|
},
|
|
&data,
|
|
)
|
|
.expect("display device should be parsed");
|
|
|
|
assert_eq!(info.vendor_id, PCI_VENDOR_ID_INTEL);
|
|
assert_eq!(info.device_id, 0x1234);
|
|
assert_eq!(info.subsystem_device_id, 0x5678);
|
|
assert_eq!(info.irq, Some(11));
|
|
assert_eq!(info.capabilities.len(), 1);
|
|
assert_eq!(info.capabilities[0].id, PCI_CAP_ID_MSIX);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_device_info_from_config_space_rejects_short_config() {
|
|
let location = PciLocation {
|
|
segment: 0,
|
|
bus: 0,
|
|
device: 0,
|
|
function: 0,
|
|
};
|
|
assert!(parse_device_info_from_config_space(location, &[0u8; 32]).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn interrupt_support_prefers_msix_over_msi_and_legacy() {
|
|
let info = PciDeviceInfo {
|
|
location: PciLocation {
|
|
segment: 0,
|
|
bus: 0,
|
|
device: 0,
|
|
function: 0,
|
|
},
|
|
vendor_id: 0x1234,
|
|
device_id: 0x5678,
|
|
subsystem_vendor_id: 0xffff,
|
|
subsystem_device_id: 0xffff,
|
|
revision: 0,
|
|
class_code: 0,
|
|
subclass: 0,
|
|
prog_if: 0,
|
|
header_type: PCI_HEADER_TYPE_NORMAL,
|
|
irq: Some(11),
|
|
bars: Vec::new(),
|
|
capabilities: vec![
|
|
PciCapability {
|
|
id: PCI_CAP_ID_MSI,
|
|
offset: 0x50,
|
|
vendor_cap_id: None,
|
|
},
|
|
PciCapability {
|
|
id: PCI_CAP_ID_MSIX,
|
|
offset: 0x60,
|
|
vendor_cap_id: None,
|
|
},
|
|
],
|
|
};
|
|
|
|
assert_eq!(info.interrupt_support(), InterruptSupport::MsiX);
|
|
assert_eq!(info.interrupt_support().as_str(), "msix");
|
|
}
|
|
|
|
#[test]
|
|
fn interrupt_support_honors_no_msix_quirk() {
|
|
let info = PciDeviceInfo {
|
|
location: PciLocation {
|
|
segment: 0,
|
|
bus: 0,
|
|
device: 0,
|
|
function: 0,
|
|
},
|
|
vendor_id: 0x1022,
|
|
device_id: 0x145C,
|
|
subsystem_vendor_id: 0xffff,
|
|
subsystem_device_id: 0xffff,
|
|
revision: 0,
|
|
class_code: 0,
|
|
subclass: 0,
|
|
prog_if: 0,
|
|
header_type: PCI_HEADER_TYPE_NORMAL,
|
|
irq: Some(9),
|
|
bars: Vec::new(),
|
|
capabilities: vec![PciCapability {
|
|
id: PCI_CAP_ID_MSIX,
|
|
offset: 0x60,
|
|
vendor_cap_id: None,
|
|
}],
|
|
};
|
|
|
|
assert_eq!(info.interrupt_support(), InterruptSupport::LegacyOnly);
|
|
assert_eq!(info.interrupt_support().as_str(), "legacy");
|
|
}
|
|
}
|