Lazily acquire MSI-X resources

This commit is contained in:
Ruihan Li 2026-01-14 21:19:52 +08:00
parent de0f8d1e54
commit 97a77c2884
9 changed files with 266 additions and 238 deletions

1
Cargo.lock generated
View File

@ -279,6 +279,7 @@ dependencies = [
"cfg-if",
"component",
"fdt",
"int-to-c-enum",
"log",
"ostd",
"spin",

View File

@ -10,6 +10,7 @@ align_ext.workspace = true
bitflags.workspace = true
cfg-if.workspace = true
component.workspace = true
int-to-c-enum.workspace = true
log.workspace = true
ostd.workspace = true
spin.workspace = true

View File

@ -5,80 +5,83 @@
use alloc::vec::Vec;
use align_ext::AlignExt;
use int_to_c_enum::TryFromInt;
use ostd::Result;
use self::{msix::CapabilityMsixData, vendor::CapabilityVndrData};
use super::{cfg_space::Status, common_device::PciCommonDevice};
use crate::cfg_space::PciGeneralDeviceCfgOffset;
use self::{
msix::{CapabilityMsixData, RawCapabilityMsix},
vendor::{CapabilityVndrData, RawCapabilityVndr},
};
use crate::{
PciDeviceLocation,
cfg_space::{PciGeneralDeviceCfgOffset, Status},
common_device::{BarManager, PciCommonDevice},
};
pub mod msix;
pub mod vendor;
/// PCI Capability
#[derive(Debug)]
pub struct Capability {
cap_data: CapabilityData,
/// Raw PCI Capabilities.
#[derive(Debug, Default)]
pub(super) struct RawCapabilities {
msix: Option<RawCapabilityMsix>,
vndr: Vec<RawCapabilityVndr>,
}
/// PCI Capability data.
#[derive(Debug, Clone)]
pub enum CapabilityData {
/// Id:0x01, Power Management
Pm,
/// Id:0x02, Accelerated Graphics Part
Agp,
/// Id:0x03, Vital Product Data
Vpd,
/// Id:0x04, Slot Identification
SlotId,
/// Id:0x05, Message Signalled Interrupts
Msi,
/// Id:0x06, CompactPCI HotSwap
Chswp,
/// Id:0x07, PCI-X
PciX,
/// Id:0x08, HyperTransport
Hp,
/// Id:0x09, Vendor-Specific
Vndr(CapabilityVndrData),
/// Id:0x0A, Debug port
Dbg,
/// Id:0x0B, CompactPCI Central Resource Control
Ccrc,
/// Id:0x0C, PCI Standard Hot-Plug Controller
Shpc,
/// Id:0x0D, Bridge subsystem vendor/device ID
Ssvid,
/// Id:0x0R, AGP Target PCI-PCI bridge
Agp3,
/// Id:0x0F, Secure Device
Secdev,
/// Id:0x10, PCI Express
Exp,
/// Id:0x11, MSI-X
Msix(CapabilityMsixData),
/// Id:0x12, SATA Data/Index Conf
Sata,
/// Id:0x13, PCI Advanced Features
Af,
/// Id:0x14, Enhanced Allocation
Ea,
/// Id:?, Unknown
Unknown(u8),
/// PCI capability types.
#[derive(Debug, Clone, Copy, TryFromInt)]
#[repr(u8)]
enum CapabilityType {
/// Power Management
Pm = 0x01,
/// Accelerated Graphics Part
Agp = 0x02,
/// Vital Product Data
Vpd = 0x03,
/// Slot Identification
SlotId = 0x04,
/// Message Signalled Interrupts
Msi = 0x05,
/// CompactPCI HotSwap
Chswp = 0x06,
/// PCI-X
PciX = 0x07,
/// HyperTransport
Hp = 0x08,
/// Vendor-Specific
Vndr = 0x09,
/// Debug port
Dbg = 0x0A,
/// CompactPCI Central Resource Control
Ccrc = 0x0B,
/// PCI Standard Hot-Plug Controller
Shpc = 0x0C,
/// Bridge subsystem vendor/device ID
Ssvid = 0x0D,
/// AGP Target PCI-PCI bridge
Agp3 = 0x0E,
/// Secure Device
Secdev = 0x0F,
/// PCI Express
Exp = 0x10,
/// MSI-X
Msix = 0x11,
/// SATA Data/Index Conf
Sata = 0x12,
/// PCI Advanced Features
Af = 0x13,
/// Enhanced Allocation
Ea = 0x14,
}
impl Capability {
impl RawCapabilities {
/// The top of the capability position.
const CAPABILITY_TOP: u16 = 0xFC;
/// Gets the capability data
pub fn capability_data(&self) -> &CapabilityData {
&self.cap_data
}
/// Gets the capabilities of one device
pub(super) fn device_capabilities(dev: &mut PciCommonDevice) -> Vec<Self> {
/// Parses the capabilities of the PCI device.
pub(super) fn parse(dev: &PciCommonDevice) -> Self {
if !dev.read_status().contains(Status::CAPABILITIES_LIST) {
return Vec::new();
return Self::default();
}
// The offset of the first capability pointer is the same for PCI general devices and PCI
@ -87,7 +90,6 @@ impl Capability {
let mut cap_ptr =
(dev.location().read8(CAP_OFFSET) as u16).align_down(align_of::<u32>() as _);
let mut cap_ptr_vec = Vec::new();
let mut capabilities = Vec::new();
// Read all capability pointers so that it is easy for us to get the length of each
// capability.
@ -100,39 +102,60 @@ impl Capability {
// Push the top position so that we can calculate the length of the last capability.
cap_ptr_vec.push(Self::CAPABILITY_TOP);
let mut caps = Self::default();
let length = cap_ptr_vec.len();
for i in 0..length - 1 {
let cap_ptr = cap_ptr_vec[i];
let next_ptr = cap_ptr_vec[i + 1];
let cap_type = dev.location().read8(cap_ptr);
let data = match cap_type {
0x01 => CapabilityData::Pm,
0x02 => CapabilityData::Agp,
0x03 => CapabilityData::Vpd,
0x04 => CapabilityData::SlotId,
0x05 => CapabilityData::Msi,
0x06 => CapabilityData::Chswp,
0x07 => CapabilityData::PciX,
0x08 => CapabilityData::Hp,
0x09 => {
CapabilityData::Vndr(CapabilityVndrData::new(dev, cap_ptr, next_ptr - cap_ptr))
}
0x0A => CapabilityData::Dbg,
0x0B => CapabilityData::Ccrc,
0x0C => CapabilityData::Shpc,
0x0D => CapabilityData::Ssvid,
0x0E => CapabilityData::Agp3,
0x0F => CapabilityData::Secdev,
0x10 => CapabilityData::Exp,
0x11 => CapabilityData::Msix(CapabilityMsixData::new(dev, cap_ptr)),
0x12 => CapabilityData::Sata,
0x13 => CapabilityData::Af,
0x14 => CapabilityData::Ea,
_ => CapabilityData::Unknown(cap_type),
let raw_cap_type = dev.location().read8(cap_ptr);
let Ok(cap_type) = CapabilityType::try_from(raw_cap_type) else {
continue;
};
capabilities.push(Self { cap_data: data });
match cap_type {
CapabilityType::Msix => {
// "More than one MSI-X Capability structure per Function is prohibited."
if caps.msix.is_some() {
log::warn!(
"superfluous MSI-X Capability structures at {:?} are ignored",
dev.location()
);
continue;
}
caps.msix = Some(RawCapabilityMsix::parse(dev, cap_ptr));
}
CapabilityType::Vndr => {
caps.vndr
.push(RawCapabilityVndr::new(cap_ptr, next_ptr - cap_ptr));
}
_ => {}
}
}
capabilities
caps
}
/// Acquires a new [`CapabilityMsixData`] instance.
pub(super) fn acquire_msix_data(
&self,
loc: &PciDeviceLocation,
bar_manager: &mut BarManager,
) -> Result<Option<CapabilityMsixData>> {
let Some(raw_msix) = self.msix.as_ref() else {
return Ok(None);
};
Ok(Some(CapabilityMsixData::new(loc, bar_manager, raw_msix)?))
}
/// Iterates over [`CapabilityVndrData`] instances.
pub(super) fn iter_vndr_data(
&self,
loc: &PciDeviceLocation,
) -> impl Iterator<Item = CapabilityVndrData> {
self.vndr
.iter()
.map(|raw_vndr| CapabilityVndrData::new(loc, raw_vndr))
}
}

View File

@ -4,16 +4,42 @@
use alloc::vec::Vec;
use ostd::{io::IoMem, irq::IrqLine, mm::VmIoOnce};
use ostd::{Error, Result, io::IoMem, irq::IrqLine, mm::VmIoOnce};
use crate::{
PciDeviceLocation,
arch::{MSIX_DEFAULT_MSG_ADDR, construct_remappable_msix_address},
cfg_space::{BarAccess, Command},
common_device::PciCommonDevice,
cfg_space::{BarAccess, Command, PciCommonCfgOffset},
common_device::{BarManager, PciCommonDevice},
};
/// MSI-X capability. It will set the BAR space it uses to be hidden.
/// Raw information about MSI-X capability.
#[derive(Debug)]
pub(super) struct RawCapabilityMsix {
cap_ptr: u16,
msg_ctrl: u16,
table_info: u32,
pba_info: u32,
}
impl RawCapabilityMsix {
pub(super) fn parse(dev: &PciCommonDevice, cap_ptr: u16) -> Self {
let msg_ctrl = dev.location().read16(cap_ptr + 2);
let table_info = dev.location().read32(cap_ptr + 4);
let pba_info = dev.location().read32(cap_ptr + 8);
Self {
cap_ptr,
msg_ctrl,
table_info,
pba_info,
}
}
}
/// MSI-X capability.
///
/// It will acquire the access to the BAR space it uses.
#[derive(Debug)]
pub struct CapabilityMsixData {
loc: PciDeviceLocation,
@ -23,69 +49,41 @@ pub struct CapabilityMsixData {
/// | Vector Control: u32 | Msg Data: u32 | Msg Upper Addr: u32 | Msg Addr: u32 |
table_bar: IoMem,
/// Pending bits table.
#[expect(dead_code)]
pending_table_bar: IoMem,
table_offset: usize,
#[expect(dead_code)]
pending_table_offset: usize,
irqs: Vec<Option<IrqLine>>,
}
impl Clone for CapabilityMsixData {
fn clone(&self) -> Self {
let new_vec = self.irqs.clone().to_vec();
Self {
loc: self.loc,
ptr: self.ptr,
table_size: self.table_size,
table_bar: self.table_bar.clone(),
pending_table_bar: self.pending_table_bar.clone(),
irqs: new_vec,
table_offset: self.table_offset,
pending_table_offset: self.pending_table_offset,
}
}
}
impl CapabilityMsixData {
pub(super) fn new(dev: &mut PciCommonDevice, cap_ptr: u16) -> Self {
// Get Table and PBA offset, provide functions to modify them
let table_info = dev.location().read32(cap_ptr + 4);
let pba_info = dev.location().read32(cap_ptr + 8);
let table_bar;
let pba_bar;
let bar_manager = dev.bar_manager_mut();
match bar_manager
.bar_mut((pba_info & 0b111) as u8)
.expect("MSIX cfg:pba BAR is none")
.acquire()
.expect("MSIX cfg:pba BAR is unavailable")
pub(super) fn new(
loc: &PciDeviceLocation,
bar_manager: &mut BarManager,
raw_cap: &RawCapabilityMsix,
) -> Result<Self> {
let pba_bar = match bar_manager
.bar_mut((raw_cap.pba_info & 0b111) as u8)
.ok_or(Error::InvalidArgs)?
.acquire()?
{
BarAccess::Memory(io_mem) => {
pba_bar = io_mem.clone();
}
BarAccess::Io => {
panic!("MSIX cfg:pba BAR is IO type")
}
BarAccess::Memory(io_mem) => io_mem,
BarAccess::Io => return Err(Error::InvalidArgs),
};
match bar_manager
.bar_mut((table_info & 0b111) as u8)
.expect("MSIX cfg:table BAR is none")
.acquire()
.expect("MSIX cfg:table BAR is unavailable")
let pba_offset = (raw_cap.pba_info & !(0b111u32)) as usize;
let table_bar = match bar_manager
.bar_mut((raw_cap.table_info & 0b111) as u8)
.ok_or(Error::InvalidArgs)?
.acquire()?
{
BarAccess::Memory(io_mem) => {
table_bar = io_mem.clone();
}
BarAccess::Io => {
panic!("MSIX cfg:table BAR is IO type")
}
}
BarAccess::Memory(io_mem) => io_mem,
BarAccess::Io => return Err(Error::InvalidArgs),
};
let table_offset = (raw_cap.table_info & !(0b111u32)) as usize;
let pba_offset = (pba_info & !(0b111u32)) as usize;
let table_offset = (table_info & !(0b111u32)) as usize;
let table_size = (dev.location().read16(cap_ptr + 2) & 0b11_1111_1111) + 1;
let table_size = (raw_cap.msg_ctrl & 0b11_1111_1111) + 1;
// Set the message address and disable all MSI-X vectors.
let message_address = MSIX_DEFAULT_MSG_ADDR;
@ -103,32 +101,37 @@ impl CapabilityMsixData {
}
// Enable MSI-X (bit 15: MSI-X Enable).
dev.location()
.write16(cap_ptr + 2, dev.location().read16(cap_ptr + 2) | 0x8000);
loc.write16(
raw_cap.cap_ptr + 2,
loc.read16(raw_cap.cap_ptr + 2) | 0x8000,
);
// Disable INTx. Enable bus master.
dev.write_command(dev.read_command() | Command::INTERRUPT_DISABLE | Command::BUS_MASTER);
loc.write16(
PciCommonCfgOffset::Command as u16,
loc.read16(PciCommonCfgOffset::Command as u16)
| (Command::INTERRUPT_DISABLE | Command::BUS_MASTER).bits(),
);
let mut irqs = Vec::with_capacity(table_size as usize);
for _ in 0..table_size {
irqs.push(None);
}
Self {
loc: *dev.location(),
ptr: cap_ptr,
table_size: (dev.location().read16(cap_ptr + 2) & 0b11_1111_1111) + 1,
Ok(Self {
loc: *loc,
ptr: raw_cap.cap_ptr,
table_size,
table_bar,
pending_table_bar: pba_bar,
irqs,
table_offset,
pending_table_offset: pba_offset,
}
})
}
/// Returns the size of the MSI-X Table.
pub fn table_size(&self) -> u16 {
// bit 10:0 table size
(self.loc.read16(self.ptr + 2) & 0b11_1111_1111) + 1
self.table_size
}
/// Enables an interrupt line.

View File

@ -4,10 +4,24 @@
use ostd::{Error, Result};
use crate::{PciDeviceLocation, common_device::PciCommonDevice};
use crate::PciDeviceLocation;
/// Vendor specific capability. Users can access this capability area at will,
/// except for the PCI configuration space which cannot be accessed at will through this structure.
/// Raw information about vendor-specific capability.
#[derive(Debug)]
pub(super) struct RawCapabilityVndr {
cap_ptr: u16,
length: u16,
}
impl RawCapabilityVndr {
pub(super) fn new(cap_ptr: u16, length: u16) -> Self {
Self { cap_ptr, length }
}
}
/// Vendor-specific capability.
///
/// Users can access this capability area at will.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct CapabilityVndrData {
location: PciDeviceLocation,
@ -16,11 +30,11 @@ pub struct CapabilityVndrData {
}
impl CapabilityVndrData {
pub(super) fn new(dev: &PciCommonDevice, cap_ptr: u16, length: u16) -> Self {
pub(super) fn new(loc: &PciDeviceLocation, raw_cap: &RawCapabilityVndr) -> Self {
Self {
location: *dev.location(),
cap_ptr,
length,
location: *loc,
cap_ptr: raw_cap.cap_ptr,
length: raw_cap.length,
}
}

View File

@ -2,16 +2,12 @@
//! PCI device common definitions or functions.
use alloc::vec::Vec;
use ostd::Result;
use super::{
capability::Capability,
cfg_space::{AddrLen, Bar, Command, Status},
device_info::PciDeviceId,
};
use crate::{
cfg_space::{PciBridgeCfgOffset, PciCommonCfgOffset},
device_info::PciDeviceLocation,
capability::{RawCapabilities, msix::CapabilityMsixData, vendor::CapabilityVndrData},
cfg_space::{AddrLen, Bar, Command, PciBridgeCfgOffset, PciCommonCfgOffset, Status},
device_info::{PciDeviceId, PciDeviceLocation},
};
/// PCI common device.
@ -23,7 +19,7 @@ pub struct PciCommonDevice {
location: PciDeviceLocation,
header_type: PciHeaderType,
bar_manager: BarManager,
capabilities: Vec<Capability>,
capabilities: RawCapabilities,
}
impl PciCommonDevice {
@ -47,16 +43,6 @@ impl PciCommonDevice {
&mut self.bar_manager
}
/// Returns the PCI capabilities.
pub fn capabilities(&self) -> &Vec<Capability> {
&self.capabilities
}
/// Returns the PCI capabilities and a mutable reference to the BAR manager.
pub fn capabilities_and_bar_manager_mut(&mut self) -> (&Vec<Capability>, &mut BarManager) {
(&self.capabilities, &mut self.bar_manager)
}
/// Returns the PCI device type.
pub fn device_type(&self) -> PciDeviceType {
self.header_type.device_type()
@ -83,20 +69,41 @@ impl PciCommonDevice {
Status::from_bits_truncate(self.location.read16(PciCommonCfgOffset::Status as u16))
}
/// Acquires necessary resources to build the MSI-X capability data, if the capability exists.
///
/// Note that the MSI-X capability data occupies some memory BARs. Therefore, it will fail if
/// the necessary resources are not available.
pub fn acquire_msix_capability(&mut self) -> Result<Option<CapabilityMsixData>> {
self.capabilities
.acquire_msix_data(&self.location, &mut self.bar_manager)
}
/// Gets access to the vendor-specific capability data.
pub fn iter_vndr_capability(&self) -> impl Iterator<Item = CapabilityVndrData> {
self.capabilities.iter_vndr_data(&self.location)
}
/// Gets access to the vendor-specific capability data with a mutable reference to the BAR
/// manager.
pub fn iter_vndr_capability_with_bar_manager(
&mut self,
) -> (impl Iterator<Item = CapabilityVndrData>, &mut BarManager) {
(
self.capabilities.iter_vndr_data(&self.location),
&mut self.bar_manager,
)
}
pub(super) fn new(location: PciDeviceLocation) -> Option<Self> {
if location.read16(0) == 0xFFFF {
// No device.
return None;
}
let capabilities = Vec::new();
let device_id = PciDeviceId::new(location);
let bar_manager = BarManager {
bars: [const { None }; 6],
};
let mut header_type =
PciHeaderType::try_from_raw(location.read8(PciCommonCfgOffset::HeaderType as u16))?;
if let PciDeviceType::PciToPciBridge(primary_bus, secondary_bus, subordinate_bus) =
&mut header_type.device_type
{
@ -105,6 +112,11 @@ impl PciCommonDevice {
*subordinate_bus = location.read8(PciBridgeCfgOffset::SubordinateBusNumber as u16);
}
let bar_manager = BarManager {
bars: [const { None }; 6],
};
let capabilities = RawCapabilities::default();
let mut device = Self {
device_id,
location,
@ -121,7 +133,7 @@ impl PciCommonDevice {
device.bar_manager = BarManager::new(device.header_type.device_type(), location);
device.write_command(command_val | (Command::MEMORY_SPACE | Command::IO_SPACE));
device.capabilities = Capability::device_capabilities(&mut device);
device.capabilities = RawCapabilities::parse(&device);
Some(device)
}

View File

@ -4,8 +4,7 @@ use alloc::{boxed::Box, sync::Arc};
use core::fmt::Debug;
use aster_pci::{
PciDeviceId, bus::PciDevice, capability::CapabilityData, cfg_space::BarAccess,
common_device::PciCommonDevice,
PciDeviceId, bus::PciDevice, cfg_space::BarAccess, common_device::PciCommonDevice,
};
use aster_util::{field_ptr, safe_ptr::SafePtr};
use log::{info, warn};
@ -284,15 +283,12 @@ impl VirtioPciModernTransport {
info!("[Virtio]: Found device:{:?}", device_type);
let mut msix = None;
let mut notify = None;
let mut common_cfg = None;
let mut device_cfg = None;
let (caps, bar_manager) = common_device.capabilities_and_bar_manager_mut();
for cap in caps {
match cap.capability_data() {
CapabilityData::Vndr(vendor) => {
let data = VirtioPciCapabilityData::new(bar_manager, *vendor);
let (vndr_caps, bar_manager) = common_device.iter_vndr_capability_with_bar_manager();
for vndr_cap in vndr_caps {
let data = VirtioPciCapabilityData::new(bar_manager, vndr_cap);
match data.typ() {
VirtioPciCpabilityType::CommonCfg => {
common_cfg = Some(VirtioPciCommonCfg::new(&data));
@ -311,23 +307,14 @@ impl VirtioPciModernTransport {
VirtioPciCpabilityType::PciCfg => {}
}
}
CapabilityData::Msix(data) => {
msix = Some(data.clone());
}
CapabilityData::Unknown(id) => {
panic!("unknown capability: {}", id)
}
_ => {
panic!("PCI Virtio device should not have other type of capability")
}
}
}
// TODO: Support interrupt without MSI-X
let msix = msix.unwrap();
let notify = notify.unwrap();
let common_cfg = common_cfg.unwrap();
let device_cfg = device_cfg.unwrap();
// TODO: Support interrupt without MSI-X.
let msix = common_device.acquire_msix_capability().unwrap().unwrap();
let msix_manager = VirtioMsixManager::new(msix);
Ok(Self {
common_device,
common_cfg,

View File

@ -4,7 +4,6 @@ use alloc::{boxed::Box, collections::vec_deque::VecDeque, sync::Arc};
use aster_pci::{
bus::{PciDevice, PciDriver},
capability::CapabilityData,
common_device::PciCommonDevice,
};
use ostd::{bus::BusProbeError, sync::SpinLock};
@ -42,10 +41,7 @@ impl PciDriver for VirtioPciDriver {
return Err((BusProbeError::DeviceNotMatch, device));
}
let has_vendor_cap = device
.capabilities()
.iter()
.any(|cap| matches!(cap.capability_data(), CapabilityData::Vndr(_)));
let has_vendor_cap = device.iter_vndr_capability().next().is_some();
let device_id = *device.device_id();
let transport: Box<dyn VirtioTransport> = match device_id.device_id {
0x1000..0x1040 if (device.device_id().revision_id == 0) => {

View File

@ -3,7 +3,7 @@
use alloc::{boxed::Box, sync::Arc};
use core::fmt::Debug;
use aster_pci::{capability::CapabilityData, cfg_space::BarAccess, common_device::PciCommonDevice};
use aster_pci::{cfg_space::BarAccess, common_device::PciCommonDevice};
use aster_util::safe_ptr::SafePtr;
use log::{info, warn};
use ostd::{
@ -112,17 +112,8 @@ impl VirtioPciLegacyTransport {
num_queues += 1;
}
// TODO: Support interrupt without MSI-X
let mut msix = None;
for cap in common_device.capabilities().iter() {
match cap.capability_data() {
CapabilityData::Msix(data) => {
msix = Some(data.clone());
}
_ => continue,
}
}
let Some(msix) = msix else {
// TODO: Support interrupt without MSI-X.
let Ok(Some(msix)) = common_device.acquire_msix_capability() else {
return Err((BusProbeError::ConfigurationSpaceError, common_device));
};
let msix_manager = VirtioMsixManager::new(msix);