diff --git a/Cargo.lock b/Cargo.lock index 099d5c16aa..36e8864505 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1518,6 +1518,7 @@ dependencies = [ "bitflags 2.5.0", "endian-num", "num_enum", + "pci_types", "volatile 0.6.1", "volatile-macro", "zerocopy", diff --git a/src/drivers/pci.rs b/src/drivers/pci.rs index 1ea8b0ee71..be590c6276 100644 --- a/src/drivers/pci.rs +++ b/src/drivers/pci.rs @@ -86,6 +86,10 @@ impl PciDevice { Self { address, access } } + pub fn access(&self) -> &T { + &self.access + } + pub fn header(&self) -> PciHeader { PciHeader::new(self.address) } diff --git a/src/drivers/virtio/transport/pci.rs b/src/drivers/virtio/transport/pci.rs index 4291fc85eb..3a77eac295 100644 --- a/src/drivers/virtio/transport/pci.rs +++ b/src/drivers/virtio/transport/pci.rs @@ -10,7 +10,7 @@ use core::{mem, ptr}; use pci_types::capability::PciCapability; use virtio_spec::pci::{ - CommonCfg, CommonCfgVolatileFieldAccess, CommonCfgVolatileWideFieldAccess, + Cap, CapCfgType, CommonCfg, CommonCfgVolatileFieldAccess, CommonCfgVolatileWideFieldAccess, IsrStatus as IsrStatusRaw, }; use virtio_spec::{le32, DeviceStatus}; @@ -39,13 +39,13 @@ use crate::drivers::virtio::error::VirtioError; pub struct Origin { cfg_ptr: u16, // Register to be read to reach configuration structure of type cfg_type dev_id: u16, - cap_struct: PciCapRaw, + cap_struct: Cap, } /// Maps a given device specific pci configuration structure and /// returns a static reference to it. pub fn map_dev_cfg(cap: &PciCap) -> Option<&'static mut T> { - if cap.cfg_type != virtio_spec::pci::Cap::DeviceCfg { + if cap.cfg_type != CapCfgType::Device { error!("Capability of device config has wrong id. Mapping not possible..."); return None; }; @@ -84,10 +84,10 @@ pub fn map_dev_cfg(cap: &PciCap) -> Option<&'static mut T> { /// as it is not directly mapped into address space from PCI device /// configuration space. /// Therefore the struct only contains necessary information to map -/// corresponding config type (`pci::Cap`) into address space. +/// corresponding config type into address space. #[derive(Clone)] pub struct PciCap { - cfg_type: virtio_spec::pci::Cap, + cfg_type: CapCfgType, bar: PciBar, id: u8, offset: MemOff, @@ -172,50 +172,11 @@ impl PciCap { } } -/// Virtio's PCI capabilities structure. -/// See Virtio specification v.1.1 - 4.1.4 -/// -/// WARN: endianness of this structure should be seen as little endian. -/// As this structure is not meant to be used outside of this module and for -/// ease of conversion from reading data into struct from PCI configuration -/// space, no conversion is made for struct fields. -#[derive(Clone, Debug)] -#[repr(C)] -struct PciCapRaw { - cap_vndr: u8, - cap_next: u8, - cap_len: u8, - cfg_type: u8, - bar_index: u8, - id: u8, - padding: [u8; 2], - offset: u32, - length: u32, -} - -// This only shows compiler, that structs are identical -// with themselves. -impl Eq for PciCapRaw {} - -// In order to compare two PciCapRaw structs PartialEq is needed -impl PartialEq for PciCapRaw { - fn eq(&self, other: &Self) -> bool { - self.cap_vndr == other.cap_vndr - && self.cap_next == other.cap_next - && self.cap_len == other.cap_len - && self.cfg_type == other.cfg_type - && self.bar_index == other.bar_index - && self.id == other.id - && self.offset == other.offset - && self.length == other.length - } -} - /// Universal Caplist Collections holds all universal capability structures for /// a given Virtio PCI device. /// /// As Virtio's PCI devices are allowed to present multiple capability -/// structures of the same config type (`pci::Cap`), the structure +/// structures of the same config type, the structure /// provides a driver with all capabilities, sorted in descending priority, /// allowing the driver to choose. /// The structure contains a special dev_cfg_list field, a vector holding @@ -597,7 +558,7 @@ impl NotifCfg { // Assumes the cap_len is a multiple of 8 // This read MIGHT be slow, as it does NOT ensure 32 bit alignment. let notify_off_multiplier_ptr = - cap.origin.cfg_ptr + u16::try_from(mem::size_of::()).unwrap(); + cap.origin.cfg_ptr + u16::try_from(mem::size_of::()).unwrap(); let notify_off_multiplier = cap.device.read_register(notify_off_multiplier_ptr); // define base memory address from which the actual Queue Notify address can be derived via @@ -785,23 +746,24 @@ impl ShMemCfg { // Assumes the cap_len is a multiple of 8 // This read MIGHT be slow, as it does NOT ensure 32 bit alignment. - let offset_hi_ptr = - cap.origin.cfg_ptr + u16::try_from(mem::size_of::()).unwrap(); + let offset_hi_ptr = cap.origin.cfg_ptr + u16::try_from(mem::size_of::()).unwrap(); let offset_hi = cap.device.read_register(offset_hi_ptr); // Create 64 bit offset from high and low 32 bit values - let offset = - MemOff::from((u64::from(offset_hi) << 32) ^ u64::from(cap.origin.cap_struct.offset)); + let offset = MemOff::from( + (u64::from(offset_hi) << 32) ^ u64::from(cap.origin.cap_struct.offset.to_ne()), + ); // Assumes the cap_len is a multiple of 8 // This read MIGHT be slow, as it does NOT ensure 32 bit alignment. let length_hi_ptr = cap.origin.cfg_ptr - + u16::try_from(mem::size_of::() + mem::size_of::()).unwrap(); + + u16::try_from(mem::size_of::() + mem::size_of::()).unwrap(); let length_hi = cap.device.read_register(length_hi_ptr); // Create 64 bit length from high and low 32 bit values - let length = - MemLen::from((u64::from(length_hi) << 32) ^ u64::from(cap.origin.cap_struct.length)); + let length = MemLen::from( + (u64::from(length_hi) << 32) ^ u64::from(cap.origin.cap_struct.length.to_ne()), + ); let virt_addr_raw = cap.bar.mem_addr + offset; let raw_ptr = ptr::with_exposed_provenance_mut::(virt_addr_raw.into()); @@ -885,36 +847,6 @@ impl PciBar { } } -/// Reads a raw capability struct [PciCapRaw] out of a PCI device's configuration space. -fn read_cap_raw(device: &PciDevice, register: u16) -> PciCapRaw { - let mut quadruple_word: [u8; 16] = [0; 16]; - - debug!("Converting read word from PCI device config space into native endian bytes."); - - // Write words sequentially into array - for i in 0..4 { - // Read word need to be converted to little endian bytes as PCI is little endian. - // Interpretation of multi byte values needs to be swapped for big endian machines - let word: [u8; 4] = device.read_register(register + 4 * i).to_le_bytes(); - let i = 4 * i as usize; - quadruple_word[i..i + 4].copy_from_slice(&word); - } - - PciCapRaw { - cap_vndr: quadruple_word[0], - cap_next: quadruple_word[1], - cap_len: quadruple_word[2], - cfg_type: quadruple_word[3], - bar_index: quadruple_word[4], - id: quadruple_word[5], - // Unwrapping is okay here, as transformed array slice is always 2 * u8 long and initialized - padding: quadruple_word[6..8].try_into().unwrap(), - // Unwrapping is okay here, as transformed array slice is always 4 * u8 long and initialized - offset: u32::from_le_bytes(quadruple_word[8..12].try_into().unwrap()), - length: u32::from_le_bytes(quadruple_word[12..16].try_into().unwrap()), - } -} - /// Reads all PCI capabilities, starting at the capabilities list pointer from the /// PCI device. /// @@ -933,17 +865,17 @@ fn read_caps( PciCapability::Vendor(capability) => Some(capability), _ => None, }) - .map(|capability| (capability.offset, read_cap_raw(device, capability.offset))) - .filter(|(_ptr, capability)| capability.cfg_type != virtio_spec::pci::Cap::PciCfg.into()) + .map(|addr| { + let cap = Cap::read(addr.clone(), device.access()).unwrap(); + (addr.offset, cap) + }) + .filter(|(_ptr, capability)| capability.cfg_type != CapCfgType::Pci.into()) .map(|(ptr, capability)| PciCap { - cfg_type: virtio_spec::pci::Cap::from(capability.cfg_type), - bar: *bars - .iter() - .find(|bar| bar.index == capability.bar_index) - .unwrap(), + cfg_type: CapCfgType::from(capability.cfg_type), + bar: *bars.iter().find(|bar| bar.index == capability.bar).unwrap(), id: capability.id, - offset: MemOff::from(capability.offset), - length: MemLen::from(capability.length), + offset: MemOff::from(capability.offset.to_ne()), + length: MemLen::from(capability.length.to_ne()), device: *device, origin: Origin { cfg_ptr: ptr, @@ -1003,36 +935,36 @@ pub(crate) fn map_caps(device: &PciDevice) -> Result match pci_cap.map_common_cfg() { + CapCfgType::Common => match pci_cap.map_common_cfg() { Some(cap) => caps.add_cfg_common(ComCfg::new(cap, pci_cap.id)), None => error!( "Common config capability with id {}, of device {:x}, could not be mapped!", pci_cap.id, device_id ), }, - virtio_spec::pci::Cap::NotifyCfg => match NotifCfg::new(&pci_cap) { + CapCfgType::Notify => match NotifCfg::new(&pci_cap) { Some(notif) => caps.add_cfg_notif(notif), None => error!( "Notification config capability with id {}, of device {:x} could not be used!", pci_cap.id, device_id ), }, - virtio_spec::pci::Cap::IsrCfg => match pci_cap.map_isr_status() { + CapCfgType::Isr => match pci_cap.map_isr_status() { Some(isr_stat) => caps.add_cfg_isr(IsrStatus::new(isr_stat, pci_cap.id)), None => error!( "ISR status config capability with id {}, of device {:x} could not be used!", pci_cap.id, device_id ), }, - virtio_spec::pci::Cap::PciCfg => caps.add_cfg_alt(PciCfgAlt::new(&pci_cap)), - virtio_spec::pci::Cap::SharedMemoryCfg => match ShMemCfg::new(&pci_cap) { + CapCfgType::Pci => caps.add_cfg_alt(PciCfgAlt::new(&pci_cap)), + CapCfgType::SharedMemory => match ShMemCfg::new(&pci_cap) { Some(sh_mem) => caps.add_cfg_sh_mem(sh_mem), None => error!( "Shared Memory config capability with id {}, of device {:x} could not be used!", pci_cap.id, device_id ), }, - virtio_spec::pci::Cap::DeviceCfg => caps.add_cfg_dev(pci_cap), + CapCfgType::Device => caps.add_cfg_dev(pci_cap), // PCI's configuration space is allowed to hold other structures, which are not virtio specific and are therefore ignored // in the following diff --git a/virtio-spec/Cargo.toml b/virtio-spec/Cargo.toml index 9496ff8eab..41fafe51c2 100644 --- a/virtio-spec/Cargo.toml +++ b/virtio-spec/Cargo.toml @@ -12,6 +12,7 @@ categories = ["no-std", "no-std::no-alloc"] bitflags = "2" endian-num = { version = "0.1", features = ["bitflags", "linux-types"] } num_enum = { version = "0.7", default-features = false } +pci_types = "0.7" volatile = "0.6" volatile-macro = "0.6" zerocopy = { version = "0.7", optional = true, default-features = false } diff --git a/virtio-spec/src/pci.rs b/virtio-spec/src/pci.rs index 0191b5bb91..c8bc70908d 100644 --- a/virtio-spec/src/pci.rs +++ b/virtio-spec/src/pci.rs @@ -1,6 +1,11 @@ //! Definitions for Virtio over PCI bus. +use core::mem; + +use endian_num::{le64, Le}; use num_enum::{FromPrimitive, IntoPrimitive}; +use pci_types::capability::PciCapabilityAddress; +use pci_types::ConfigRegionAccess; use volatile::access::{ReadOnly, ReadWrite, RestrictAccess}; use volatile::VolatilePtr; use volatile_macro::VolatileFieldAccess; @@ -8,6 +13,284 @@ use volatile_macro::VolatileFieldAccess; use crate::volatile::WideVolatilePtr; use crate::{le16, le32, DeviceStatus}; +/// PCI Capability +/// +/// See [`CapData`] for reading additional fields. +#[doc(alias = "virtio_pci_cap")] +#[cfg_attr( + feature = "zerocopy", + derive(zerocopy_derive::FromZeroes, zerocopy_derive::FromBytes) +)] +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct Cap { + /// Generic PCI field: `PCI_CAP_ID_VNDR` + /// + /// 0x09; Identifies a vendor-specific capability. + pub cap_vndr: u8, + + /// Generic PCI field: next ptr. + /// + /// Link to next capability in the capability list in the PCI configuration space. + pub cap_next: u8, + + /// Generic PCI field: capability length + /// + /// Length of this capability structure, including the whole of + /// struct virtio_pci_cap, and extra data if any. + /// This length MAY include padding, or fields unused by the driver. + pub cap_len: u8, + + /// Identifies the structure. + /// + /// Each structure is detailed individually below. + /// + /// The device MAY offer more than one structure of any type - this makes it + /// possible for the device to expose multiple interfaces to drivers. The order of + /// the capabilities in the capability list specifies the order of preference + /// suggested by the device. A device may specify that this ordering mechanism be + /// overridden by the use of the `id` field. + /// + ///
+ /// + /// For example, on some hypervisors, notifications using IO accesses are + /// faster than memory accesses. In this case, the device would expose two + /// capabilities with `cfg_type` set to VIRTIO_PCI_CAP_NOTIFY_CFG: + /// the first one addressing an I/O BAR, the second one addressing a memory BAR. + /// In this example, the driver would use the I/O BAR if I/O resources are available, and fall back on + /// memory BAR when I/O resources are unavailable. + /// + ///
+ pub cfg_type: u8, + + /// Where to find it. + /// + /// values 0x0 to 0x5 specify a Base Address register (BAR) belonging to + /// the function located beginning at 10h in PCI Configuration Space + /// and used to map the structure into Memory or I/O Space. + /// The BAR is permitted to be either 32-bit or 64-bit, it can map Memory Space + /// or I/O Space. + /// + /// Any other value is reserved for future use. + pub bar: u8, + + /// Multiple capabilities of the same type + /// + /// Used by some device types to uniquely identify multiple capabilities + /// of a certain type. If the device type does not specify the meaning of + /// this field, its contents are undefined. + pub id: u8, + + /// Pad to full dword. + pub padding: [u8; 2], + + /// Offset within bar. + /// + /// indicates where the structure begins relative to the base address associated + /// with the BAR. The alignment requirements of `offset` are indicated + /// in each structure-specific section below. + pub offset: le32, + + /// Length of the structure, in bytes. + /// + /// indicates the length of the structure. + /// + /// `length` MAY include padding, or fields unused by the driver, or + /// future extensions. + /// + ///
+ /// + /// For example, a future device might present a large structure size of several + /// MBytes. + /// As current devices never utilize structures larger than 4KBytes in size, + /// driver MAY limit the mapped structure size to e.g. + /// 4KBytes (thus ignoring parts of structure after the first + /// 4KBytes) to allow forward compatibility with such devices without loss of + /// functionality and without wasting resources. + /// + ///
+ pub length: le32, +} + +impl Cap { + pub fn read(addr: PciCapabilityAddress, access: &impl ConfigRegionAccess) -> Option { + let data = unsafe { access.read(addr.address, addr.offset) }; + let [cap_vndr, _cap_next, cap_len, _cfg_type] = data.to_ne_bytes(); + + if cap_vndr != 0x09 { + return None; + } + + if cap_len < 16 { + return None; + } + + let data = [ + data, + unsafe { access.read(addr.address, addr.offset + 4) }, + unsafe { access.read(addr.address, addr.offset + 8) }, + unsafe { access.read(addr.address, addr.offset + 12) }, + ]; + + let this = unsafe { mem::transmute::<[u32; 4], Self>(data) }; + + Some(this) + } +} + +/// PCI Capability 64 +#[doc(alias = "virtio_pci_cap64")] +#[cfg_attr( + feature = "zerocopy", + derive(zerocopy_derive::FromZeroes, zerocopy_derive::FromBytes) +)] +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct Cap64 { + pub cap: Cap, + pub offset_hi: le32, + pub length_hi: le32, +} + +/// PCI Notify Capability +#[doc(alias = "virtio_pci_notify_cap")] +#[cfg_attr( + feature = "zerocopy", + derive(zerocopy_derive::FromZeroes, zerocopy_derive::FromBytes) +)] +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct NotifyCap { + pub cap: Cap, + + /// Multiplier for queue_notify_off. + pub notify_off_multiplier: le32, +} + +/// PCI Configuration Capability +#[doc(alias = "virtio_pci_cfg_cap")] +#[cfg_attr( + feature = "zerocopy", + derive(zerocopy_derive::FromZeroes, zerocopy_derive::FromBytes) +)] +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct CfgCap { + pub cap: Cap, + + /// Data for BAR access. + pub pci_cfg_data: [u8; 4], +} + +/// PCI Capability Data +#[derive(Clone, Copy, Debug)] +pub struct CapData { + /// Identifies the structure. + pub cfg_type: CapCfgType, + + /// Where to find it. + pub bar: u8, + + /// Multiple capabilities of the same type + pub id: u8, + + /// Offset within bar. + pub offset: le64, + + /// Length of the structure, in bytes. + pub length: le64, + + /// Multiplier for queue_notify_off. + pub notify_off_multiplier: Option, +} + +impl CapData { + pub fn read(addr: PciCapabilityAddress, access: &impl ConfigRegionAccess) -> Option { + let cap = Cap::read(addr.clone(), access)?; + let cfg_type = CapCfgType::from(cap.cfg_type); + + let (offset, length) = match cfg_type { + CapCfgType::SharedMemory => { + if cap.cap_len < 24 { + return None; + } + + let offset_hi = unsafe { access.read(addr.address, addr.offset + 16) }; + let offset_hi = Le(offset_hi); + let offset = le64::from([cap.offset, offset_hi]); + + let length_hi = unsafe { access.read(addr.address, addr.offset + 20) }; + let length_hi = Le(length_hi); + let length = le64::from([cap.length, length_hi]); + + (offset, length) + } + _ => (le64::from(cap.offset), le64::from(cap.length)), + }; + + let notify_off_multiplier = match cfg_type { + CapCfgType::Notify => { + if cap.cap_len < 20 { + return None; + } + + let notify_off_multiplier = unsafe { access.read(addr.address, addr.offset + 16) }; + let notify_off_multiplier = Le(notify_off_multiplier); + + Some(notify_off_multiplier) + } + _ => None, + }; + + Some(Self { + cfg_type, + bar: cap.bar, + id: cap.id, + offset, + length, + notify_off_multiplier, + }) + } +} + +/// PCI Capability Configuration Type +#[derive(IntoPrimitive, FromPrimitive, PartialEq, Eq, Clone, Copy, Debug)] +#[non_exhaustive] +#[repr(u8)] +pub enum CapCfgType { + /// Common configuration + #[doc(alias = "VIRTIO_PCI_CAP_COMMON_CFG")] + Common = 1, + + /// Notifications + #[doc(alias = "VIRTIO_PCI_CAP_NOTIFY_CFG")] + Notify = 2, + + /// ISR Status + #[doc(alias = "VIRTIO_PCI_CAP_ISR_CFG")] + Isr = 3, + + /// Device specific configuration + #[doc(alias = "VIRTIO_PCI_CAP_DEVICE_CFG")] + Device = 4, + + /// PCI configuration access + #[doc(alias = "VIRTIO_PCI_CAP_PCI_CFG")] + Pci = 5, + + /// Shared memory region + #[doc(alias = "VIRTIO_PCI_CAP_SHARED_MEMORY_CFG")] + SharedMemory = 8, + + /// Vendor-specific data + #[doc(alias = "VIRTIO_PCI_CAP_VENDOR_CFG")] + Vendor = 9, + + /// Unknown device + #[num_enum(catch_all)] + Unknown(u8), +} + /// Common configuration structure /// /// The common configuration structure is found at the bar and offset within the [`VIRTIO_PCI_CAP_COMMON_CFG`] capability. @@ -155,41 +438,3 @@ virtio_bitflags! { const DEVICE_CONFIGURATION_INTERRUPT = 1 << 1; } } - -/// PCI Capability Configuration Type -#[derive(IntoPrimitive, FromPrimitive, PartialEq, Eq, Clone, Copy, Debug)] -#[non_exhaustive] -#[repr(u8)] -pub enum Cap { - /// Common configuration - #[doc(alias = "VIRTIO_PCI_CAP_COMMON_CFG")] - CommonCfg = 1, - - /// Notifications - #[doc(alias = "VIRTIO_PCI_CAP_NOTIFY_CFG")] - NotifyCfg = 2, - - /// ISR Status - #[doc(alias = "VIRTIO_PCI_CAP_ISR_CFG")] - IsrCfg = 3, - - /// Device specific configuration - #[doc(alias = "VIRTIO_PCI_CAP_DEVICE_CFG")] - DeviceCfg = 4, - - /// PCI configuration access - #[doc(alias = "VIRTIO_PCI_CAP_PCI_CFG")] - PciCfg = 5, - - /// Shared memory region - #[doc(alias = "VIRTIO_PCI_CAP_SHARED_MEMORY_CFG")] - SharedMemoryCfg = 8, - - /// Vendor-specific data - #[doc(alias = "VIRTIO_PCI_CAP_VENDOR_CFG")] - VencodCfg = 9, - - /// Unknown device - #[num_enum(catch_all)] - Unknown(u8), -}