diff --git a/src/capability/mod.rs b/src/capability/mod.rs new file mode 100644 index 0000000..5354bf8 --- /dev/null +++ b/src/capability/mod.rs @@ -0,0 +1,134 @@ +use crate::{ConfigRegionAccess, PciAddress}; +use bit_field::BitField; +use core::fmt::Formatter; + +mod msi; + +pub use msi::{MsiCapability, MultipleMessageSupport, TriggerMode}; + +#[derive(Clone)] +pub struct PciCapabilityAddress { + pub address: PciAddress, + pub offset: u16, +} + +impl core::fmt::Debug for PciCapabilityAddress { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "{}, offset: {:02x}", self.address, self.offset) + } +} + +/// PCI capabilities +#[derive(Clone, Debug)] +pub enum PciCapability { + /// Power management capability, Cap ID = `0x01` + PowerManagement(PciCapabilityAddress), + /// Accelerated graphics port capability, Cap ID = `0x02` + AcceleratedGraphicsPort(PciCapabilityAddress), + /// Vital product data capability, Cap ID = `0x3` + VitalProductData(PciCapabilityAddress), + /// Slot identification capability, Cap ID = `0x04` + SlotIdentification(PciCapabilityAddress), + /// Message signalling interrupts capability, Cap ID = `0x05` + Msi(MsiCapability), + /// CompactPCI HotSwap capability, Cap ID = `0x06` + CompactPCIHotswap(PciCapabilityAddress), + /// PCI-X capability, Cap ID = `0x07` + PciX(PciCapabilityAddress), + /// HyperTransport capability, Cap ID = `0x08` + HyperTransport(PciCapabilityAddress), + /// Vendor-specific capability, Cap ID = `0x09` + Vendor(PciCapabilityAddress), + /// Debug port capability, Cap ID = `0x0A` + DebugPort(PciCapabilityAddress), + /// CompactPCI Central Resource Control capability, Cap ID = `0x0B` + CompactPCICentralResourceControl(PciCapabilityAddress), + /// PCI Standard Hot-Plug Controller capability, Cap ID = `0x0C` + PciHotPlugControl(PciCapabilityAddress), + /// Bridge subsystem vendor/device ID capability, Cap ID = `0x0D` + BridgeSubsystemVendorId(PciCapabilityAddress), + /// AGP Target PCI-PCI bridge capability, Cap ID = `0x0E` + AGP3(PciCapabilityAddress), + /// PCI Express capability, Cap ID = `0x10` + PciExpress(PciCapabilityAddress), + /// MSI-X capability, Cap ID = `0x11` + MsiX(PciCapabilityAddress), + /// Unknown capability + Unknown { + address: PciCapabilityAddress, + id: u8, + }, +} + +impl PciCapability { + fn parse(id: u8, address: PciCapabilityAddress, extension: u16) -> Option { + match id { + 0x00 => None, // null capability + 0x01 => Some(PciCapability::PowerManagement(address)), + 0x02 => Some(PciCapability::AcceleratedGraphicsPort(address)), + 0x03 => Some(PciCapability::VitalProductData(address)), + 0x04 => Some(PciCapability::SlotIdentification(address)), + 0x05 => Some(PciCapability::Msi(MsiCapability::new(address, extension))), + 0x06 => Some(PciCapability::CompactPCIHotswap(address)), + 0x07 => Some(PciCapability::PciX(address)), + 0x08 => Some(PciCapability::HyperTransport(address)), + 0x09 => Some(PciCapability::Vendor(address)), + 0x0A => Some(PciCapability::DebugPort(address)), + 0x0B => Some(PciCapability::CompactPCICentralResourceControl(address)), + 0x0C => Some(PciCapability::PciHotPlugControl(address)), + 0x0D => Some(PciCapability::BridgeSubsystemVendorId(address)), + 0x0E => Some(PciCapability::AGP3(address)), + 0x10 => Some(PciCapability::PciExpress(address)), + 0x11 => Some(PciCapability::MsiX(address)), + _ => Some(PciCapability::Unknown { address, id }), + } + } +} + +pub struct CapabilityIterator<'a, T: ConfigRegionAccess> { + address: PciAddress, + offset: u16, + access: &'a T, +} + +impl<'a, T: ConfigRegionAccess> CapabilityIterator<'a, T> { + pub(crate) fn new( + address: PciAddress, + offset: u16, + access: &'a T, + ) -> CapabilityIterator<'a, T> { + CapabilityIterator { + address, + offset, + access, + } + } +} + +impl<'a, T: ConfigRegionAccess> Iterator for CapabilityIterator<'a, T> { + type Item = PciCapability; + + fn next(&mut self) -> Option { + loop { + if self.offset == 0 { + return None; + } + let data = unsafe { self.access.read(self.address, self.offset) }; + let next_ptr = data.get_bits(8..16); + let id = data.get_bits(0..8); + let extension = data.get_bits(16..32) as u16; + let cap = PciCapability::parse( + id as u8, + PciCapabilityAddress { + address: self.address, + offset: self.offset, + }, + extension, + ); + self.offset = next_ptr as u16; + if let Some(cap) = cap { + return Some(cap); + } + } + } +} diff --git a/src/capability/msi.rs b/src/capability/msi.rs new file mode 100644 index 0000000..7f870f3 --- /dev/null +++ b/src/capability/msi.rs @@ -0,0 +1,185 @@ +use crate::{capability::PciCapabilityAddress, ConfigRegionAccess}; +use bit_field::BitField; +use core::convert::TryFrom; + +/// Specifies how many MSI interrupts one device can have. +/// Device will modify lower bits of interrupt vector to send multiple messages, so interrupt block +/// must be aligned accordingly. +#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] +pub enum MultipleMessageSupport { + /// Device can send 1 interrupt. No interrupt vector modification is happening here + Int1 = 0b000, + /// Device can send 2 interrupts + Int2 = 0b001, + /// Device can send 4 interrupts + Int4 = 0b010, + /// Device can send 8 interrupts + Int8 = 0b011, + /// Device can send 16 interrupts + Int16 = 0b100, + /// Device can send 32 interrupts + Int32 = 0b101, +} + +impl TryFrom for MultipleMessageSupport { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + 0b000 => Ok(MultipleMessageSupport::Int1), + 0b001 => Ok(MultipleMessageSupport::Int2), + 0b010 => Ok(MultipleMessageSupport::Int4), + 0b011 => Ok(MultipleMessageSupport::Int8), + 0b100 => Ok(MultipleMessageSupport::Int16), + 0b101 => Ok(MultipleMessageSupport::Int32), + _ => Err(()), + } + } +} + +/// When device should trigger the interrupt +#[derive(Debug)] +pub enum TriggerMode { + Edge = 0b00, + LevelAssert = 0b11, + LevelDeassert = 0b10, +} + +#[derive(Debug, Clone)] +pub struct MsiCapability { + address: PciCapabilityAddress, + per_vector_masking: bool, + is_64bit: bool, + multiple_message_capable: MultipleMessageSupport, +} + +impl MsiCapability { + pub(crate) fn new(address: PciCapabilityAddress, control: u16) -> MsiCapability { + MsiCapability { + address, + per_vector_masking: control.get_bit(8), + is_64bit: control.get_bit(7), + multiple_message_capable: + MultipleMessageSupport::try_from(control.get_bits(1..4) as u8) + .unwrap_or(MultipleMessageSupport::Int1), + } + } + + /// Does device supports masking individual vectors? + #[inline] + pub fn has_per_vector_masking(&self) -> bool { + self.per_vector_masking + } + + /// Is device using 64-bit addressing? + #[inline] + pub fn is_64bit(&self) -> bool { + self.is_64bit + } + + /// How many interrupts this device has? + #[inline] + pub fn get_multiple_message_capable(&self) -> MultipleMessageSupport { + self.multiple_message_capable + } + + /// Is MSI capability enabled? + pub fn is_enabled(&self, access: &impl ConfigRegionAccess) -> bool { + let reg = unsafe { access.read(self.address.address, self.address.offset) }; + reg.get_bit(0) + } + + /// Enable or disable MSI capability + pub fn set_enabled(&self, enabled: bool, access: &impl ConfigRegionAccess) { + let mut reg = unsafe { access.read(self.address.address, self.address.offset) }; + reg.set_bit(0, enabled); + unsafe { access.write(self.address.address, self.address.offset, reg) }; + } + + /// Set how many interrupts the device will use. If requested count is bigger than supported count, + /// the second will be used. + pub fn set_multiple_message_enable( + &self, + data: MultipleMessageSupport, + access: &impl ConfigRegionAccess, + ) { + let mut reg = unsafe { access.read(self.address.address, self.address.offset) }; + reg.set_bits(4..7, (data.min(self.multiple_message_capable)) as u32); + unsafe { access.write(self.address.address, self.address.offset, reg) }; + } + + /// Return how many interrupts the device is using + pub fn get_multiple_message_enable( + &self, + access: &impl ConfigRegionAccess, + ) -> MultipleMessageSupport { + let reg = unsafe { access.read(self.address.address, self.address.offset) }; + MultipleMessageSupport::try_from(reg.get_bits(4..7) as u8) + .unwrap_or(MultipleMessageSupport::Int1) + } + + /// Set where the interrupts will be sent to + /// + /// # Arguments + /// * `address` - Target Local APIC address (if not changed, can be calculated with `0xFEE00000 | (processor << 12)`) + /// * `vector` - Which interrupt vector should be triggered on LAPIC + /// * `trigger_mode` - When interrupt should be triggered + /// * `access` - PCI Configuration Space accessor + pub fn set_message_info( + &self, + address: u32, + vector: u8, + trigger_mode: TriggerMode, + access: &impl ConfigRegionAccess, + ) { + unsafe { access.write(self.address.address, self.address.offset + 0x4, address) } + let data_offset = if self.is_64bit { 0xC } else { 0x8 }; + let mut data = + unsafe { access.read(self.address.address, self.address.offset + data_offset) }; + data.set_bits(0..8, vector as u32); + data.set_bits(14..16, trigger_mode as u32); + unsafe { + access.write( + self.address.address, + self.address.offset + data_offset, + data, + ) + } + } + + /// Get interrupt mask + /// + /// # Note + /// Only supported on when device supports 64-bit addressing and per-vector masking. Otherwise + /// returns `0` + pub fn get_message_mask(&self, access: &impl ConfigRegionAccess) -> u32 { + if self.is_64bit && self.per_vector_masking { + unsafe { access.read(self.address.address, self.address.offset + 0x10) } + } else { + 0 + } + } + + /// Set interrupt mask + /// + /// # Note + /// Only supported on when device supports 64-bit addressing and per-vector masking. Otherwise + /// will do nothing + pub fn set_message_mask(&self, access: &impl ConfigRegionAccess, mask: u32) { + if self.is_64bit && self.per_vector_masking { + unsafe { access.write(self.address.address, self.address.offset + 0x10, mask) } + } + } + + /// Get pending interrupts + /// + /// # Note + /// Only supported on when device supports 64-bit addressing. Otherwise will return `0` + pub fn get_pending(&self, access: &impl ConfigRegionAccess) -> u32 { + if self.is_64bit { + unsafe { access.read(self.address.address, self.address.offset + 0x14) } + } else { + 0 + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 219ba40..69d0d8b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,12 @@ #![no_std] +pub mod capability; pub mod device_type; +mod register; +pub use register::{DevselTiming, StatusRegister}; + +use crate::capability::CapabilityIterator; use bit_field::BitField; use core::fmt; @@ -45,7 +50,14 @@ impl PciAddress { impl fmt::Display for PciAddress { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:02x}-{:02x}:{:02x}.{}", self.segment(), self.bus(), self.device(), self.function()) + write!( + f, + "{:02x}-{:02x}:{:02x}.{}", + self.segment(), + self.bus(), + self.device(), + self.function() + ) } } @@ -98,7 +110,10 @@ impl PciHeader { pub fn id(&self, access: &impl ConfigRegionAccess) -> (VendorId, DeviceId) { let id = unsafe { access.read(self.0, 0x00) }; - (id.get_bits(0..16) as VendorId, id.get_bits(16..32) as DeviceId) + ( + id.get_bits(0..16) as VendorId, + id.get_bits(16..32) as DeviceId, + ) } pub fn header_type(&self, access: &impl ConfigRegionAccess) -> HeaderType { @@ -128,6 +143,11 @@ impl PciHeader { field.get_bits(8..16) as Interface, ) } + + pub fn status(&self, access: &impl ConfigRegionAccess) -> StatusRegister { + let data = unsafe { access.read(self.0, 0x4).get_bits(16..32) }; + StatusRegister::new(data as u16) + } } /// Endpoints have a Type-0 header, so the remainder of the header is of the form: @@ -179,17 +199,42 @@ impl PciHeader { pub struct EndpointHeader(PciAddress); impl EndpointHeader { - pub fn from_header(header: PciHeader, access: &impl ConfigRegionAccess) -> Option { + pub fn from_header( + header: PciHeader, + access: &impl ConfigRegionAccess, + ) -> Option { match header.header_type(access) { 0x00 => Some(EndpointHeader(header.0)), _ => None, } } + pub fn status(&self, access: &impl ConfigRegionAccess) -> StatusRegister { + let data = unsafe { access.read(self.0, 0x4).get_bits(16..32) }; + StatusRegister::new(data as u16) + } + pub fn header(&self) -> PciHeader { PciHeader(self.0) } + pub fn capability_pointer(&self, access: &impl ConfigRegionAccess) -> u16 { + let status = self.status(access); + if status.has_capability_list() { + unsafe { access.read(self.0, 0x34).get_bits(0..8) as u16 } + } else { + 0 + } + } + + pub fn capabilities<'a, T: ConfigRegionAccess>( + &self, + access: &'a T, + ) -> CapabilityIterator<'a, T> { + let pointer = self.capability_pointer(access); + CapabilityIterator::new(self.0, pointer, access) + } + /// Get the contents of a BAR in a given slot. Empty bars will return `None`. /// /// ### Note @@ -224,7 +269,11 @@ impl EndpointHeader { }; match bar.get_bits(1..3) { - 0b00 => Some(Bar::Memory32 { address, size, prefetchable }), + 0b00 => Some(Bar::Memory32 { + address, + size, + prefetchable, + }), 0b10 => { let address = { let mut address = address as u64; @@ -232,13 +281,19 @@ impl EndpointHeader { address.set_bits(32..64, unsafe { access.read(self.0, offset + 4) } as u64); address }; - Some(Bar::Memory64 { address, size: size as u64, prefetchable }) + Some(Bar::Memory64 { + address, + size: size as u64, + prefetchable, + }) } // TODO: should we bother to return an error here? _ => panic!("BAR Memory type is reserved!"), } } else { - Some(Bar::Io { port: bar.get_bits(2..32) }) + Some(Bar::Io { + port: bar.get_bits(2..32), + }) } } } @@ -247,7 +302,17 @@ pub const MAX_BARS: usize = 6; #[derive(Clone, Copy, Debug)] pub enum Bar { - Memory32 { address: u32, size: u32, prefetchable: bool }, - Memory64 { address: u64, size: u64, prefetchable: bool }, - Io { port: u32 }, + Memory32 { + address: u32, + size: u32, + prefetchable: bool, + }, + Memory64 { + address: u64, + size: u64, + prefetchable: bool, + }, + Io { + port: u32, + }, } diff --git a/src/register.rs b/src/register.rs new file mode 100644 index 0000000..c7ded09 --- /dev/null +++ b/src/register.rs @@ -0,0 +1,128 @@ +use bit_field::BitField; +use core::convert::TryFrom; +use core::fmt::{Debug, Formatter}; + +/// Slowest time that a device will assert DEVSEL# for any bus command except Configuration Space +/// read and writes +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum DevselTiming { + Fast = 0x0, + Medium = 0x1, + Slow = 0x2, +} + +impl TryFrom for DevselTiming { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + 0x0 => Ok(DevselTiming::Fast), + 0x1 => Ok(DevselTiming::Medium), + 0x2 => Ok(DevselTiming::Slow), + _ => Err(()), + } + } +} + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct StatusRegister(u16); + +impl StatusRegister { + pub fn new(value: u16) -> Self { + StatusRegister(value) + } + + /// Will be `true` whenever the device detects a parity error, even if parity error handling is disabled. + pub fn parity_error_detected(&self) -> bool { + self.0.get_bit(15) + } + + /// Will be `true` whenever the device asserts SERR#. + pub fn signalled_system_error(&self) -> bool { + self.0.get_bit(14) + } + + /// Will return `true`, by a master device, whenever its transaction + /// (except for Special Cycle transactions) is terminated with Master-Abort. + pub fn received_master_abort(&self) -> bool { + self.0.get_bit(13) + } + + /// Will return `true`, by a master device, whenever its transaction is terminated with Target-Abort. + pub fn received_target_abort(&self) -> bool { + self.0.get_bit(12) + } + + /// Will return `true` whenever a target device terminates a transaction with Target-Abort. + pub fn signalled_target_abort(&self) -> bool { + self.0.get_bit(11) + } + + /// The slowest time that a device will assert DEVSEL# for any bus command except + /// Configuration Space read and writes. + /// + /// For PCIe always set to `Fast` + pub fn devsel_timing(&self) -> Result { + let bits = self.0.get_bits(9..11); + DevselTiming::try_from(bits as u8) + } + + /// This returns `true` only when the following conditions are met: + /// - The bus agent asserted PERR# on a read or observed an assertion of PERR# on a write + /// - the agent setting the bit acted as the bus master for the operation in which the error occurred + /// - bit 6 of the Command register (Parity Error Response bit) is set to 1. + pub fn master_data_parity_error(&self) -> bool { + self.0.get_bit(8) + } + + /// If returns `true` the device can accept fast back-to-back transactions that are not from + /// the same agent; otherwise, transactions can only be accepted from the same agent. + /// + /// For PCIe always set to `false` + pub fn fast_back_to_back_capable(&self) -> bool { + self.0.get_bit(7) + } + + /// If returns `true` the device is capable of running at 66 MHz; otherwise, the device runs at 33 MHz. + /// + /// For PCIe always set to `false` + pub fn capable_66mhz(&self) -> bool { + self.0.get_bit(5) + } + + /// If returns `true` the device implements the pointer for a New Capabilities Linked list; + /// otherwise, the linked list is not available. + /// + /// For PCIe always set to `true` + pub fn has_capability_list(&self) -> bool { + self.0.get_bit(4) + } + + /// Represents the state of the device's INTx# signal. If returns `true` and bit 10 of the + /// Command register (Interrupt Disable bit) is set to 0 the signal will be asserted; + /// otherwise, the signal will be ignored. + pub fn interrupt_status(&self) -> bool { + self.0.get_bit(3) + } +} + +impl Debug for StatusRegister { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_struct("StatusRegister") + .field("parity_error_detected", &self.parity_error_detected()) + .field("signalled_system_error", &self.signalled_system_error()) + .field("received_master_abort", &self.received_master_abort()) + .field("received_target_abort", &self.received_target_abort()) + .field("signalled_target_abort", &self.signalled_target_abort()) + .field("devsel_timing", &self.devsel_timing()) + .field("master_data_parity_error", &self.master_data_parity_error()) + .field( + "fast_back_to_back_capable", + &self.fast_back_to_back_capable(), + ) + .field("capable_66mhz", &self.capable_66mhz()) + .field("has_capability_list", &self.has_capability_list()) + .field("interrupt_status", &self.interrupt_status()) + .finish() + } +}