--- /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) + +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 } +} + +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); + Self { address, data } + } + + pub fn validate(&self) -> bool { + MsiAddress::validate(self.address.raw) + && self.data.vector() >= 32 + && self.data.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, +} + +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, + } + } +} + +#[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, +} + +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 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); + } +}