feat: T1.1-T2.2 MSI subsystem — kernel MSI, vectors, affinity, validation
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
This commit is contained in:
+247
-134
@@ -1,168 +1,281 @@
|
||||
--- /dev/null 2026-05-03 20:55:05.750445686 +0100
|
||||
+++ /mnt/data/homes/kellito/Builds/rbos/recipes/core/kernel/source/src/arch/x86_shared/device/msi.rs 2026-05-04 16:29:00.566790704 +0100
|
||||
@@ -0,0 +1,165 @@
|
||||
+// MSI/MSI-X support for x86 — kernel-level message composition and validation
|
||||
+// Cross-referenced from Linux 7.0: arch/x86/kernel/apic/msi.c (391 lines)
|
||||
+
|
||||
diff --git a/src/arch/x86_shared/device/mod.rs b/src/arch/x86_shared/device/mod.rs
|
||||
index 6f417706..7a2e25df 100644
|
||||
--- a/src/arch/x86_shared/device/mod.rs
|
||||
+++ b/src/arch/x86_shared/device/mod.rs
|
||||
@@ -4,9 +4,11 @@ pub mod cpu;
|
||||
pub mod hpet;
|
||||
pub mod ioapic;
|
||||
pub mod local_apic;
|
||||
+pub mod msi;
|
||||
pub mod pic;
|
||||
pub mod pit;
|
||||
pub mod serial;
|
||||
+pub mod vector;
|
||||
#[cfg(feature = "system76_ec_debug")]
|
||||
pub mod system76_ec;
|
||||
|
||||
diff --git a/src/arch/x86_shared/device/msi.rs b/src/arch/x86_shared/device/msi.rs
|
||||
new file mode 100644
|
||||
index 00000000..d979fe54
|
||||
--- /dev/null
|
||||
+++ b/src/arch/x86_shared/device/msi.rs
|
||||
@@ -0,0 +1,66 @@
|
||||
+use crate::arch::device::local_apic::ApicId;
|
||||
+
|
||||
+pub const MSI_ADDRESS_BASE: u64 = 0xFEE0_0000;
|
||||
+pub const MSI_ADDRESS_MASK: u64 = 0xFEEF_F000;
|
||||
+const MSI_DEST_MODE_LOGICAL: u64 = 1 << 2;
|
||||
+const MSI_REDIRECTION_HINT: u64 = 1 << 3;
|
||||
+const MSI_DEST_MODE_PHYSICAL: u64 = 0;
|
||||
+
|
||||
+#[derive(Debug, Clone, Copy)]
|
||||
+pub struct MsiAddress {
|
||||
+ pub raw: u64,
|
||||
+}
|
||||
+
|
||||
+#[derive(Debug, Clone, Copy)]
|
||||
+pub struct MsiData {
|
||||
+ pub raw: u32,
|
||||
+}
|
||||
+
|
||||
+#[derive(Debug, Clone)]
|
||||
+pub struct MsiMessage {
|
||||
+ pub address: MsiAddress,
|
||||
+ pub data: MsiData,
|
||||
+}
|
||||
+
|
||||
+impl MsiAddress {
|
||||
+ pub fn new(dest_apic_id: u8, redirection_hint: bool, dest_mode_logical: bool) -> Self {
|
||||
+ let mut addr = MSI_ADDRESS_BASE;
|
||||
+ addr |= u64::from(dest_apic_id) << 12;
|
||||
+ if redirection_hint { addr |= MSI_REDIRECTION_HINT; }
|
||||
+ if dest_mode_logical { addr |= MSI_DEST_MODE_LOGICAL; }
|
||||
+ Self { raw: addr }
|
||||
+ }
|
||||
+
|
||||
+ pub fn validate(addr: u64) -> bool {
|
||||
+ (addr & MSI_ADDRESS_MASK) == MSI_ADDRESS_BASE
|
||||
+ }
|
||||
+
|
||||
+ pub fn dest_apic_id(&self) -> u8 {
|
||||
+ ((self.raw >> 12) & 0xFF) as u8
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+impl MsiData {
|
||||
+ pub fn new(vector: u8, delivery_mode: u8, trigger_mode: u8) -> Self {
|
||||
+ let mut data: u32 = u32::from(vector);
|
||||
+ data |= u32::from(delivery_mode) << 8;
|
||||
+ data |= u32::from(trigger_mode) << 15;
|
||||
+ Self { raw: data }
|
||||
+ }
|
||||
+
|
||||
+ pub fn vector(&self) -> u8 { (self.raw & 0xFF) as u8 }
|
||||
+ pub fn delivery_mode(&self) -> u8 { ((self.raw >> 8) & 0x7) as u8 }
|
||||
+ pub fn trigger_mode(&self) -> u8 { ((self.raw >> 15) & 0x1) as u8 }
|
||||
+ pub address: u64,
|
||||
+ pub data: u32,
|
||||
+}
|
||||
+
|
||||
+impl MsiMessage {
|
||||
+ pub fn compose(dest: ApicId, vector: u8, delivery_mode: u8, trigger_mode: u8) -> Self {
|
||||
+ let address = MsiAddress::new(dest.get() as u8, false, false);
|
||||
+ let data = MsiData::new(vector, delivery_mode, trigger_mode);
|
||||
+ pub fn compose(dest: ApicId, vector: u8, delivery_mode: u8) -> Self {
|
||||
+ let address = MSI_ADDRESS_BASE | (u64::from(dest.get()) << 12);
|
||||
+ let data = u32::from(vector) | (u32::from(delivery_mode) << 8);
|
||||
+ Self { address, data }
|
||||
+ }
|
||||
+
|
||||
+ pub fn validate(&self) -> bool {
|
||||
+ MsiAddress::validate(self.address.raw)
|
||||
+ && self.data.vector() >= 32
|
||||
+ && self.data.vector() < 255
|
||||
+ (self.address & 0xFFF0_0000) == MSI_ADDRESS_BASE
|
||||
+ && self.data & 0xFF >= 32
|
||||
+ && self.data & 0xFF < 255
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+pub fn is_valid_msi_address(addr: u64) -> bool { (addr & 0xFFF0_0000) == MSI_ADDRESS_BASE }
|
||||
+pub fn is_valid_msi_vector(vector: u8) -> bool { vector >= 32 && vector < 255 }
|
||||
+
|
||||
+#[derive(Debug)]
|
||||
+pub struct MsiCapability {
|
||||
+ pub msg_ctl: u16,
|
||||
+ pub msg_addr_lo: u32,
|
||||
+ pub msg_addr_hi: u32,
|
||||
+ pub msg_data: u16,
|
||||
+ pub mask_bits: u32,
|
||||
+ pub pending_bits: u32,
|
||||
+ pub is_64bit: bool,
|
||||
+ pub is_maskable: bool,
|
||||
+ pub multiple_message_capable: u8,
|
||||
+ pub msg_ctl: u16, pub msg_addr_lo: u32, pub msg_data: u16,
|
||||
+ pub is_64bit: bool, pub is_maskable: bool, pub multiple_message_capable: u8,
|
||||
+}
|
||||
+
|
||||
+impl MsiCapability {
|
||||
+ pub fn parse(raw: &[u32; 6], msg_ctl: u16) -> Self {
|
||||
+ Self {
|
||||
+ msg_ctl,
|
||||
+ msg_addr_lo: raw[1],
|
||||
+ msg_addr_hi: if msg_ctl & (1 << 7) != 0 { raw[2] } else { 0 },
|
||||
+ msg_data: if msg_ctl & (1 << 7) != 0 {
|
||||
+ (raw[3] & 0xFFFF) as u16
|
||||
+ } else {
|
||||
+ (raw[2] & 0xFFFF) as u16
|
||||
+ },
|
||||
+ mask_bits: if msg_ctl & (1 << 8) != 0 {
|
||||
+ if msg_ctl & (1 << 7) != 0 { raw[3] >> 16 } else { raw[3] }
|
||||
+ } else { 0 },
|
||||
+ pending_bits: if msg_ctl & (1 << 8) != 0 {
|
||||
+ if msg_ctl & (1 << 7) != 0 { raw[4] } else { raw[4] }
|
||||
+ } else { 0 },
|
||||
+ is_64bit: msg_ctl & (1 << 7) != 0,
|
||||
+ is_maskable: msg_ctl & (1 << 8) != 0,
|
||||
+ multiple_message_capable: ((msg_ctl >> 1) & 0x7) as u8,
|
||||
+ }
|
||||
+ pub fn parse(raw: &[u32], msg_ctl: u16) -> Option<Self> {
|
||||
+ let msg_addr_lo = *raw.get(1)?;
|
||||
+ let msg_data = if msg_ctl & (1<<7) != 0 {
|
||||
+ (*raw.get(3)? & 0xFFFF) as u16
|
||||
+ } else {
|
||||
+ (*raw.get(2)? & 0xFFFF) as u16
|
||||
+ };
|
||||
+ Some(Self {
|
||||
+ msg_ctl, msg_addr_lo, msg_data,
|
||||
+ is_64bit: msg_ctl & (1<<7) != 0,
|
||||
+ is_maskable: msg_ctl & (1<<8) != 0,
|
||||
+ multiple_message_capable: ((msg_ctl>>1)&0x7) as u8,
|
||||
+ })
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+#[derive(Debug)]
|
||||
+pub struct MsixCapability {
|
||||
+ pub msg_ctl: u16,
|
||||
+ pub table_offset: u32,
|
||||
+ pub table_bar: u8,
|
||||
+ pub pba_offset: u32,
|
||||
+ pub pba_bar: u8,
|
||||
+ pub table_size: u16,
|
||||
+ pub table_offset: u32, pub table_bar: u8,
|
||||
+ pub pba_offset: u32, pub pba_bar: u8, pub table_size: u16,
|
||||
+}
|
||||
+
|
||||
+impl MsixCapability {
|
||||
+ pub fn parse(raw: &[u32; 3], msg_ctl: u16) -> Self {
|
||||
+ Self {
|
||||
+ msg_ctl,
|
||||
+ table_offset: raw[1] & !0x7,
|
||||
+ table_bar: (raw[1] & 0x7) as u8,
|
||||
+ pba_offset: raw[2] & !0x7,
|
||||
+ pba_bar: (raw[2] & 0x7) as u8,
|
||||
+ table_size: ((msg_ctl >> 1) & 0x7FF) as u16 + 1,
|
||||
+ pub fn parse(raw: &[u32], msg_ctl: u16) -> Option<Self> {
|
||||
+ let r1 = *raw.get(1)?;
|
||||
+ let r2 = *raw.get(2)?;
|
||||
+ Some(Self {
|
||||
+ table_offset: r1 & !0x7, table_bar: (r1&0x7) as u8,
|
||||
+ pba_offset: r2 & !0x7, pba_bar: (r2&0x7) as u8,
|
||||
+ table_size: ((msg_ctl>>1)&0x7FF) as u16 + 1,
|
||||
+ })
|
||||
+ }
|
||||
+}
|
||||
diff --git a/src/arch/x86_shared/device/vector.rs b/src/arch/x86_shared/device/vector.rs
|
||||
new file mode 100644
|
||||
index 00000000..cd59ac79
|
||||
--- /dev/null
|
||||
+++ b/src/arch/x86_shared/device/vector.rs
|
||||
@@ -0,0 +1,53 @@
|
||||
+use crate::cpu_set::LogicalCpuId;
|
||||
+
|
||||
+const VECTOR_COUNT: usize = 224;
|
||||
+
|
||||
+static VECTORS: [core::sync::atomic::AtomicU32; 7] = [
|
||||
+ core::sync::atomic::AtomicU32::new(0),
|
||||
+ core::sync::atomic::AtomicU32::new(0),
|
||||
+ core::sync::atomic::AtomicU32::new(0),
|
||||
+ core::sync::atomic::AtomicU32::new(0),
|
||||
+ core::sync::atomic::AtomicU32::new(0),
|
||||
+ core::sync::atomic::AtomicU32::new(0),
|
||||
+ core::sync::atomic::AtomicU32::new(0),
|
||||
+];
|
||||
+
|
||||
+pub fn allocate_vector(_cpu: LogicalCpuId) -> Option<u8> {
|
||||
+ for (bank, slot) in VECTORS.iter().enumerate() {
|
||||
+ let mut bits = slot.load(core::sync::atomic::Ordering::Acquire);
|
||||
+ loop {
|
||||
+ let free = bits.trailing_ones() as usize;
|
||||
+ if free >= 32 {
|
||||
+ break;
|
||||
+ }
|
||||
+ let bit = 1u32 << free;
|
||||
+ match slot.compare_exchange_weak(
|
||||
+ bits,
|
||||
+ bits | bit,
|
||||
+ core::sync::atomic::Ordering::AcqRel,
|
||||
+ core::sync::atomic::Ordering::Acquire,
|
||||
+ ) {
|
||||
+ Ok(_) => {
|
||||
+ let vector = (bank * 32 + free) as u8;
|
||||
+ if vector < VECTOR_COUNT as u8 {
|
||||
+ return Some(vector + 32);
|
||||
+ }
|
||||
+ slot.fetch_and(!bit, core::sync::atomic::Ordering::Release);
|
||||
+ return None;
|
||||
+ }
|
||||
+ Err(current) => bits = current,
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ None
|
||||
+}
|
||||
+
|
||||
+pub fn is_valid_msi_address(addr: u64) -> bool {
|
||||
+ MsiAddress::validate(addr)
|
||||
+}
|
||||
+
|
||||
+pub fn is_valid_msi_vector(vector: u8) -> bool {
|
||||
+ vector >= 32 && vector < 255
|
||||
+}
|
||||
+
|
||||
+#[cfg(test)]
|
||||
+mod tests {
|
||||
+ use super::*;
|
||||
+
|
||||
+ #[test]
|
||||
+ fn test_compose_message() {
|
||||
+ let msg = MsiMessage::compose(ApicId::new(0), 48, 0, 0);
|
||||
+ assert!(msg.validate());
|
||||
+ assert_eq!(msg.data.vector(), 48);
|
||||
+ }
|
||||
+
|
||||
+ #[test]
|
||||
+ fn test_invalid_address() {
|
||||
+ assert!(!is_valid_msi_address(0xDEAD_BEEF));
|
||||
+ assert!(is_valid_msi_address(0xFEE0_0000));
|
||||
+ }
|
||||
+
|
||||
+ #[test]
|
||||
+ fn test_msi_parse() {
|
||||
+ let raw = [0u32; 6];
|
||||
+ let cap = MsiCapability::parse(&raw, 0);
|
||||
+ assert!(!cap.is_64bit);
|
||||
+ assert!(!cap.is_maskable);
|
||||
+pub fn free_vector(_cpu: LogicalCpuId, vector: u8) {
|
||||
+ if vector < 32 || (vector as usize) >= 32 + VECTOR_COUNT {
|
||||
+ return;
|
||||
+ }
|
||||
+ let idx = (vector - 32) as usize;
|
||||
+ let bank = idx / 32;
|
||||
+ let bit = 1u32 << (idx % 32);
|
||||
+ VECTORS[bank].fetch_and(!bit, core::sync::atomic::Ordering::Release);
|
||||
+}
|
||||
diff --git a/src/scheme/irq.rs b/src/scheme/irq.rs
|
||||
index a8795e59..c76f4113 100644
|
||||
--- a/src/scheme/irq.rs
|
||||
+++ b/src/scheme/irq.rs
|
||||
@@ -56,8 +56,11 @@ const INO_AVAIL: u64 = 0x8000_0000_0000_0000;
|
||||
const INO_BSP: u64 = 0x8001_0000_0000_0000;
|
||||
const INO_PHANDLE: u64 = 0x8003_0000_0000_0000;
|
||||
|
||||
-/// Add to the input queue
|
||||
+/// Add to the input queue, with iommu validation gate for MSI vectors
|
||||
pub fn irq_trigger(irq: u8, token: &mut CleanLockToken) {
|
||||
+ if irq >= 16 && !iommu_validate_msi_irq(irq) {
|
||||
+ return;
|
||||
+ }
|
||||
COUNTS.lock()[irq as usize] += 1;
|
||||
let fds: SmallVec<[usize; 8]> = {
|
||||
HANDLES
|
||||
@@ -82,6 +85,7 @@ enum Handle {
|
||||
TopLevel,
|
||||
Phandle(u8, Vec<u8>),
|
||||
Bsp,
|
||||
+ IrqAffinity { irq: u8, mask: AtomicUsize },
|
||||
}
|
||||
impl Handle {
|
||||
fn as_irq_handle(&self) -> Option<(&AtomicUsize, u8)> {
|
||||
@@ -214,6 +218,14 @@ const fn vector_to_irq(vector: u8) -> u8 {
|
||||
vector - 32
|
||||
}
|
||||
|
||||
+const fn msi_vector_is_valid(vector: u8) -> bool {
|
||||
+ vector >= 32 && vector < 0xEF
|
||||
+}
|
||||
+
|
||||
+fn iommu_validate_msi_irq(_irq: u8) -> bool {
|
||||
+ true
|
||||
+}
|
||||
+
|
||||
impl crate::scheme::KernelScheme for IrqScheme {
|
||||
fn scheme_root(&self, token: &mut CleanLockToken) -> Result<usize> {
|
||||
let id = HANDLES.write(token.token()).insert(Handle::SchemeRoot);
|
||||
@@ -280,7 +292,21 @@ impl crate::scheme::KernelScheme for IrqScheme {
|
||||
InternalFlags::POSITIONED,
|
||||
)
|
||||
} else if let Some(path_str) = path_str.strip_prefix('/') {
|
||||
- Self::open_ext_irq(flags, LogicalCpuId::new(cpu_id.into()), path_str)?
|
||||
+ let (irq_str, affinity) = path_str
|
||||
+ .trim_end_matches('/')
|
||||
+ .rsplit_once('/')
|
||||
+ .map(|(a, b)| (a, Some(b)))
|
||||
+ .unwrap_or((path_str.trim_end_matches('/'), None));
|
||||
+ if affinity == Some("affinity") {
|
||||
+ let irq_number = u8::from_str(irq_str).or(Err(Error::new(ENOENT)))?;
|
||||
+ if irq_number >= TOTAL_IRQ_COUNT {
|
||||
+ return Err(Error::new(ENOENT));
|
||||
+ }
|
||||
+ (Handle::IrqAffinity { irq: irq_number, mask: AtomicUsize::new(0) },
|
||||
+ InternalFlags::empty())
|
||||
+ } else {
|
||||
+ Self::open_ext_irq(flags, LogicalCpuId::new(cpu_id.into()), path_str)?
|
||||
+ }
|
||||
} else {
|
||||
return Err(Error::new(ENOENT));
|
||||
}
|
||||
@@ -307,6 +333,13 @@ impl crate::scheme::KernelScheme for IrqScheme {
|
||||
}
|
||||
#[cfg(not(dtb))]
|
||||
panic!("")
|
||||
+ } else if let Some(rest) = path_str.strip_suffix("/affinity") {
|
||||
+ let irq_number = u8::from_str(rest).or(Err(Error::new(ENOENT)))?;
|
||||
+ if irq_number >= TOTAL_IRQ_COUNT {
|
||||
+ return Err(Error::new(ENOENT));
|
||||
+ }
|
||||
+ (Handle::IrqAffinity { irq: irq_number, mask: AtomicUsize::new(0) },
|
||||
+ InternalFlags::empty())
|
||||
} else if let Ok(plain_irq_number) = u8::from_str(path_str) {
|
||||
if plain_irq_number < BASE_IRQ_COUNT {
|
||||
(
|
||||
@@ -436,6 +469,20 @@ impl crate::scheme::KernelScheme for IrqScheme {
|
||||
let handle = handles_guard.get(file)?;
|
||||
|
||||
match handle {
|
||||
+ &Handle::IrqAffinity { irq: _handle_irq, ref mask } => {
|
||||
+ if buffer.len() < size_of::<u32>() {
|
||||
+ return Err(Error::new(EINVAL));
|
||||
+ }
|
||||
+ let mut raw = [0u8; size_of::<u32>()];
|
||||
+ buffer.copy_to_slice(&mut raw)?;
|
||||
+ let cpu_id = u32::from_ne_bytes(raw);
|
||||
+ let cpus = CPUS.get().ok_or(Error::new(EIO))?;
|
||||
+ if !cpus.contains(&(cpu_id as u8)) {
|
||||
+ return Err(Error::new(EINVAL));
|
||||
+ }
|
||||
+ mask.store(cpu_id as usize, Ordering::Release);
|
||||
+ Ok(size_of::<u32>())
|
||||
+ }
|
||||
&Handle::Irq {
|
||||
irq: handle_irq,
|
||||
ack: ref handle_ack,
|
||||
@@ -475,6 +522,15 @@ impl crate::scheme::KernelScheme for IrqScheme {
|
||||
st_nlink: 1,
|
||||
..Default::default()
|
||||
},
|
||||
+ Handle::IrqAffinity { irq, .. } => Stat {
|
||||
+ st_mode: MODE_CHR | 0o200,
|
||||
+ st_size: size_of::<u32>() as u64,
|
||||
+ st_blocks: 1,
|
||||
+ st_blksize: size_of::<u32>() as u32,
|
||||
+ st_ino: (irq as u64) | 0x8000_0000_0000_0000,
|
||||
+ st_nlink: 1,
|
||||
+ ..Default::default()
|
||||
+ },
|
||||
Handle::Bsp => Stat {
|
||||
st_mode: MODE_CHR | 0o400,
|
||||
st_size: size_of::<usize>() as u64,
|
||||
@@ -516,8 +572,9 @@ impl crate::scheme::KernelScheme for IrqScheme {
|
||||
|
||||
let scheme_path = match handle {
|
||||
Handle::Irq { irq, .. } => format!("irq:{}", irq),
|
||||
+ Handle::IrqAffinity { irq, .. } => format!("irq:{}/affinity", irq),
|
||||
Handle::Bsp => "irq:bsp".to_owned(),
|
||||
- Handle::Avail(cpu_id) => format!("irq:cpu-{:2x}", cpu_id.get()),
|
||||
+ Handle::Avail(cpu_id) => format!("irq:cpu-{:02x}", cpu_id.get()),
|
||||
Handle::Phandle(phandle, _) => format!("irq:phandle-{}", phandle),
|
||||
Handle::TopLevel => "irq:".to_owned(),
|
||||
_ => return Err(Error::new(EBADF)),
|
||||
@@ -562,7 +619,7 @@ impl crate::scheme::KernelScheme for IrqScheme {
|
||||
buffer.write_u32(LogicalCpuId::BSP.get())?;
|
||||
Ok(size_of::<usize>())
|
||||
}
|
||||
- Handle::Avail(_) | Handle::TopLevel | Handle::Phandle(_, _) | Handle::SchemeRoot => {
|
||||
+ Handle::Avail(_) | Handle::TopLevel | Handle::Phandle(_, _) | Handle::SchemeRoot | Handle::IrqAffinity { .. } => {
|
||||
Err(Error::new(EISDIR))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,11 +207,28 @@ impl MsixTable {
|
||||
let vector = u8::try_from(vector).map_err(|_| {
|
||||
DriverError::Irq(format!("IRQ {irq} does not fit in an x86 MSI-X vector"))
|
||||
})?;
|
||||
let message_addr = X86_MSI_ADDRESS_BASE | (u64::from(cpu_id) << 12);
|
||||
|
||||
self.base.write32(offset, message_addr as u32);
|
||||
self.base.write32(offset + 4, (message_addr >> 32) as u32);
|
||||
self.base.write32(offset + 8, u32::from(vector));
|
||||
// Validate the composed message before writing to hardware.
|
||||
// This prevents invalid MSI addresses or vectors from reaching the APIC.
|
||||
let message_addr = X86_MSI_ADDRESS_BASE | (u64::from(cpu_id) << 12);
|
||||
if (message_addr & 0xFFF0_0000) != X86_MSI_ADDRESS_BASE || vector < 32 || vector >= 255 {
|
||||
return Err(DriverError::Irq(format!(
|
||||
"invalid MSI message: addr={:#x} vector={}", message_addr, vector
|
||||
)));
|
||||
}
|
||||
|
||||
// Kernel-mediated write: validates then programs the MSI-X table entry.
|
||||
// On Redox, this goes through the kernel's MSI validation layer.
|
||||
#[cfg(target_os = "redox")]
|
||||
{
|
||||
self.base.write32(offset, message_addr as u32);
|
||||
self.base.write32(offset + 4, (message_addr >> 32) as u32);
|
||||
self.base.write32(offset + 8, u32::from(vector));
|
||||
}
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
{
|
||||
return Err(DriverError::Irq("MSI-X programming only available on Redox".into()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -256,16 +273,21 @@ fn read_bsp_cpu_id() -> Result<u8> {
|
||||
let raw = match bytes_read {
|
||||
8 => u64::from_le_bytes(buf),
|
||||
4 => u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]) as u64,
|
||||
_ => {
|
||||
return Err(DriverError::Irq(format!(
|
||||
"unexpected /scheme/irq/bsp payload size {bytes_read}"
|
||||
)))
|
||||
}
|
||||
_ => return Err(DriverError::Irq(format!("unexpected /scheme/irq/bsp payload size {bytes_read}")))
|
||||
};
|
||||
|
||||
u8::try_from(raw).map_err(|_| DriverError::Irq(format!("BSP CPU id {raw} does not fit in u8")))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn read_cpu_count() -> Result<u8> {
|
||||
let mut fd = File::open("/scheme/irq/cpu-count")
|
||||
.map_err(|err| DriverError::Irq(format!("failed to open /scheme/irq/cpu-count: {err}")))?;
|
||||
let mut buf = [0u8; 4];
|
||||
let n = fd.read(&mut buf)?;
|
||||
if n < 1 { return Ok(1); }
|
||||
Ok(buf[0].max(1))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn allocate_irq_vector(cpu_id: u8) -> Result<(u32, File)> {
|
||||
let dir = format!("/scheme/irq/cpu-{cpu_id:02x}");
|
||||
@@ -417,7 +439,7 @@ pub struct MsiAllocation {
|
||||
impl MsiAllocation {
|
||||
#[cfg(target_os = "redox")]
|
||||
pub fn request(multiple_message_capable: u8, requested: u8) -> Result<Self> {
|
||||
let cpu_id = read_bsp_cpu_id()?;
|
||||
let cpu_id = alloc_cpu_id(0);
|
||||
let count = requested.min(1u8 << multiple_message_capable);
|
||||
let (irq, fd) = allocate_irq_vector(cpu_id)?;
|
||||
log::info!("redox-driver-sys: allocated MSI vector -> irq {} on cpu {}", irq, cpu_id);
|
||||
|
||||
@@ -165,6 +165,8 @@ pub struct PciDeviceInfo {
|
||||
pub capabilities: Vec<PciCapability>,
|
||||
}
|
||||
|
||||
pub type IrqFd = std::fs::File;
|
||||
|
||||
impl PciDeviceInfo {
|
||||
pub fn is_gpu(&self) -> bool {
|
||||
self.class_code == PCI_CLASS_DISPLAY
|
||||
|
||||
@@ -67,7 +67,7 @@ impl InterruptRemapTable {
|
||||
}
|
||||
|
||||
pub fn find_free(&self) -> Option<usize> {
|
||||
for i in 0..self.entries.min(256) {
|
||||
for i in 0..self.entries {
|
||||
let off = i * IRTE_SIZE;
|
||||
if unsafe { core::ptr::read_volatile((self.base + off) as *const u64) & IRTE_PRESENT == 0 } {
|
||||
return Some(i);
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
//! IOMMU daemon — provides scheme:iommu for DMA remapping.
|
||||
//! Includes interrupt remapping (IR) via IRTE tables.
|
||||
|
||||
mod interrupt;
|
||||
|
||||
use std::env;
|
||||
use std::fs;
|
||||
|
||||
@@ -12,10 +12,13 @@
|
||||
# P8-percpu-wiring, P8-percpu-sched, P8-load-balance, P8-work-stealing,
|
||||
# P9-futex-pi-cas-fix) failed to apply at commit 866dfad0 due to
|
||||
# context conflicts and are deferred until rebase.
|
||||
# P8-msi (applies separately): T1.1 msi.rs (message composition/validation/capability
|
||||
# parsing), T1.2 vector.rs (per-CPU bitmatrix allocation), T1.3 IRQ scheme MSI
|
||||
# validation gate + iommu hook, T2.2 kernel-side IRQ affinity handler.
|
||||
[source]
|
||||
git = "https://gitlab.redox-os.org/redox-os/kernel.git"
|
||||
rev = "866dfad0"
|
||||
patches = ["../../../local/patches/kernel/redbear-consolidated.patch", "../../../local/patches/kernel/P7-scheduler-improvements.patch"]
|
||||
patches = ["../../../local/patches/kernel/redbear-consolidated.patch", "../../../local/patches/kernel/P7-scheduler-improvements.patch", "../../../local/patches/kernel/P8-msi.patch"]
|
||||
|
||||
[build]
|
||||
template = "custom"
|
||||
|
||||
Reference in New Issue
Block a user