Skip to content

Commit 5971332

Browse files
cagatay-ymkroening
andcommitted
feat(pci): MSI-X support
cloud-hypervisor only supports MSI-X interrupts for PCI devices, so support for MSI-X is needed to support running on it. Additionally, MSI-X can allow us to set separate interrupt handlers for the configuration change interrupts and queue updates (even per-queue handlers once we have multiple queues) in the future. MSI-X is not working correctly or at all on all platforms, so it is exposed as an optional feature. Co-authored-by: Martin Kröning <mkroening@posteo.net>
1 parent 15411f4 commit 5971332

6 files changed

Lines changed: 175 additions & 83 deletions

File tree

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ pci = ["virtio?/pci"]
118118
## [PCI ID]: https://pci-ids.ucw.cz/
119119
pci-ids = ["dep:pci-ids"]
120120

121+
## Causes the drivers to use _MSI-X (Message Signaled Interrupt)_ when
122+
## the PCI device supports it.
123+
msix = ["volatile/derive"]
124+
121125
## Enables semihosting support.
122126
##
123127
## Semihosting allows communicating with the host for

src/drivers/net/virtio/mmio.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ impl VirtioNetDriver<Uninit> {
3939
notif_cfg,
4040
inner: Uninit,
4141
num_vqs: 0,
42-
irq,
42+
irq: Some(irq),
4343
checksums: ChecksumCapabilities::default(),
4444
})
4545
}

src/drivers/net/virtio/mod.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ use self::error::VirtioNetError;
3333
use crate::config::VIRTIO_MAX_QUEUE_SIZE;
3434
use crate::drivers::net::virtio::constants::BUFF_PER_PACKET;
3535
use crate::drivers::net::{NetworkDriver, mtu};
36+
#[cfg(feature = "msix")]
37+
use crate::drivers::pci::MsixTableEntry;
3638
use crate::drivers::virtio::ControlRegisters;
3739
#[cfg(not(feature = "pci"))]
3840
use crate::drivers::virtio::transport::mmio::{ComCfg, IsrStatus, NotifCfg};
@@ -241,11 +243,13 @@ pub(crate) struct VirtioNetDriver<T = Init> {
241243
pub(super) com_cfg: ComCfg,
242244
pub(super) isr_stat: IsrStatus,
243245
pub(super) notif_cfg: NotifCfg,
246+
#[cfg(feature = "msix")]
247+
pub(super) msix_table: Option<VolatileRef<'static, [MsixTableEntry]>>,
244248

245249
pub(super) inner: T,
246250

247251
pub(super) num_vqs: u16,
248-
pub(super) irq: InterruptLine,
252+
pub(super) irq: Option<InterruptLine>,
249253
pub(super) checksums: ChecksumCapabilities,
250254
}
251255

@@ -485,7 +489,7 @@ impl smoltcp::phy::Device for VirtioNetDriver {
485489

486490
impl Driver for VirtioNetDriver<Init> {
487491
fn get_interrupt_number(&self) -> InterruptLine {
488-
self.irq
492+
self.irq.unwrap()
489493
}
490494

491495
fn get_name(&self) -> &'static str {
@@ -741,11 +745,30 @@ impl VirtioNetDriver<Uninit> {
741745
}
742746
debug!("{:?}", self.checksums);
743747

748+
#[cfg(feature = "msix")]
749+
if let Some(msix_table) = self.msix_table.as_mut() {
750+
warn!(
751+
"Setting up message signaled interrupts. MSI-X is known to not work on all platforms (e.g. QEMU with TAP)."
752+
);
753+
// Chosen arbitatrily. Ideally does not clash with another interrupt line.
754+
const MSIX_VECTOR: u8 = 112;
755+
let msix_entry = unsafe {
756+
msix_table
757+
.as_mut_ptr()
758+
.map(|table| table.get_unchecked_mut(0))
759+
};
760+
MsixTableEntry::configure(msix_entry, MSIX_VECTOR);
761+
self.com_cfg.select_vq(0).unwrap().set_msix_table_index(0);
762+
self.irq = Some(MSIX_VECTOR);
763+
};
764+
744765
Ok(VirtioNetDriver {
745766
dev_cfg: self.dev_cfg,
746767
com_cfg: self.com_cfg,
747768
isr_stat: self.isr_stat,
748769
notif_cfg: self.notif_cfg,
770+
#[cfg(feature = "msix")]
771+
msix_table: self.msix_table,
749772
inner,
750773
num_vqs: self.num_vqs,
751774
irq: self.irq,

src/drivers/net/virtio/pci.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ impl VirtioNetDriver<Uninit> {
3636
notif_cfg,
3737
isr_cfg,
3838
dev_cfg_list,
39+
#[cfg(feature = "msix")]
40+
msix_table,
3941
..
4042
} = caps_coll;
4143

@@ -44,14 +46,21 @@ impl VirtioNetDriver<Uninit> {
4446
return Err(error::VirtioNetError::NoDevCfg(device_id));
4547
};
4648

49+
let irq = device.get_irq();
50+
if irq.is_none() {
51+
warn!("No interrupt lanes found for virtio-net.");
52+
}
53+
4754
Ok(VirtioNetDriver {
4855
dev_cfg,
4956
com_cfg,
5057
isr_stat: isr_cfg,
5158
notif_cfg,
59+
#[cfg(feature = "msix")]
60+
msix_table,
5261
inner: Uninit,
5362
num_vqs: 0,
54-
irq: device.get_irq().unwrap(),
63+
irq,
5564
checksums: ChecksumCapabilities::default(),
5665
})
5766
}

src/drivers/pci.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,49 @@ pub(crate) fn init() {
537537
});
538538
}
539539

540+
/// MSI-X Table entry.
541+
#[cfg(feature = "msix")]
542+
#[repr(C)]
543+
#[derive(volatile::VolatileFieldAccess)]
544+
pub(crate) struct MsixTableEntry {
545+
/// Message Address
546+
message_address: u32,
547+
548+
/// Message Upper Address
549+
message_upper_address: u32,
550+
551+
/// Message Data
552+
message_data: u32,
553+
554+
/// Vector Control
555+
vector_control: u32,
556+
}
557+
558+
#[cfg(feature = "msix")]
559+
impl MsixTableEntry {
560+
#[cfg(target_arch = "x86_64")]
561+
pub fn configure(msix_entry: volatile::VolatilePtr<'_, Self>, vector: u8) {
562+
use MsixTableEntryVolatileFieldAccess;
563+
use bit_field::BitField;
564+
565+
// Mask the entry because "[s]oftware must not modify the Address, Data, or Steering Tag fields
566+
// of an entry while it is unmasked." (PCIe spec. 6.1.4.2)
567+
msix_entry
568+
.vector_control()
569+
.update(|mut control| *control.set_bit(0, true));
570+
571+
msix_entry
572+
.message_address()
573+
.update(|mut addr_low| *addr_low.set_bits(20..32, 0xfee));
574+
msix_entry
575+
.message_data()
576+
.update(|mut data| *data.set_bits(0..8, u32::from(vector) + 32));
577+
msix_entry
578+
.vector_control()
579+
.update(|mut control| *control.set_bit(0, false));
580+
}
581+
}
582+
540583
/// A module containing PCI specific errors
541584
///
542585
/// Errors include...

src/drivers/virtio/transport/pci.rs

Lines changed: 92 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@ pub struct UniCapsColl {
181181
pub(crate) notif_cfg: NotifCfg,
182182
pub(crate) isr_cfg: IsrStatus,
183183
pub(crate) dev_cfg_list: Vec<PciCap>,
184+
#[cfg(feature = "msix")]
185+
pub(crate) msix_table: Option<VolatileRef<'static, [crate::drivers::pci::MsixTableEntry]>>,
184186
}
185187
/// Wraps a [`CommonCfg`] in order to preserve
186188
/// the original structure.
@@ -256,6 +258,15 @@ impl VqCfgHandler<'_> {
256258
.write(addr.as_u64().into());
257259
}
258260

261+
#[cfg(feature = "msix")]
262+
pub fn set_msix_table_index(&mut self, index: u16) {
263+
self.select_queue();
264+
self.raw
265+
.as_mut_ptr()
266+
.queue_msix_vector()
267+
.write(index.into());
268+
}
269+
259270
pub fn notif_off(&mut self) -> u16 {
260271
self.select_queue();
261272
self.raw.as_mut_ptr().queue_notify_off().read().to_ne()
@@ -509,43 +520,6 @@ impl PciBar {
509520
}
510521
}
511522

512-
/// Reads all PCI capabilities, starting at the capabilities list pointer from the
513-
/// PCI device.
514-
///
515-
/// Returns ONLY Virtio specific capabilities, which allow to locate the actual capability
516-
/// structures inside the memory areas, indicated by the BaseAddressRegisters (BAR's).
517-
fn read_caps(device: &PciDevice<PciConfigRegion>) -> Result<Vec<PciCap>, PciError> {
518-
let device_id = device.device_id();
519-
520-
let capabilities = device
521-
.capabilities()
522-
.unwrap()
523-
.filter_map(|capability| match capability {
524-
PciCapability::Vendor(capability) => Some(capability),
525-
_ => None,
526-
})
527-
.map(|addr| CapData::read(addr, device.access()).unwrap())
528-
.filter(|cap| cap.cfg_type != CapCfgType::Pci)
529-
.flat_map(|cap| {
530-
let slot = cap.bar;
531-
device
532-
.memory_map_bar(slot, true)
533-
.map(|(addr, size)| PciCap {
534-
bar: VirtioPciBar::new(slot, addr.as_u64(), size.try_into().unwrap()),
535-
dev_id: device_id,
536-
cap,
537-
})
538-
})
539-
.collect::<Vec<_>>();
540-
541-
if capabilities.is_empty() {
542-
error!("No virtio capability found for device {device_id:x}");
543-
return Err(PciError::NoVirtioCaps(device_id));
544-
}
545-
546-
Ok(capabilities)
547-
}
548-
549523
pub(crate) fn map_caps(device: &PciDevice<PciConfigRegion>) -> Result<UniCapsColl, VirtioError> {
550524
let device_id = device.device_id();
551525

@@ -555,58 +529,94 @@ pub(crate) fn map_caps(device: &PciDevice<PciConfigRegion>) -> Result<UniCapsCol
555529
return Err(VirtioError::FromPci(PciError::NoCapPtr(device_id)));
556530
}
557531

558-
// Get list of PciCaps pointing to capabilities
559-
let cap_list = match read_caps(device) {
560-
Ok(list) => list,
561-
Err(pci_error) => return Err(VirtioError::FromPci(pci_error)),
562-
};
563-
564532
let mut com_cfg = None;
565533
let mut notif_cfg = None;
566534
let mut isr_cfg = None;
567535
let mut dev_cfg_list = Vec::new();
568-
// Map Caps in virtual memory
569-
for pci_cap in cap_list {
570-
match pci_cap.cap.cfg_type {
571-
CapCfgType::Common => {
572-
if com_cfg.is_none() {
573-
match pci_cap.map_common_cfg() {
574-
Some(cap) => com_cfg = Some(ComCfg::new(cap)),
575-
None => error!(
576-
"Common config capability of device {device_id:x} could not be mapped!"
577-
),
578-
}
536+
#[cfg(feature = "msix")]
537+
let mut msix_table = None;
538+
539+
// Reads all PCI capabilities, starting at the capabilities list pointer from the
540+
// PCI device.
541+
//
542+
// Maps ONLY Virtio specific capabilities and the MSI-X capability , which allow to locate the actual capability
543+
// structures inside the memory areas, indicated by the BaseAddressRegisters (BAR's).
544+
for capability in device.capabilities().unwrap() {
545+
match capability {
546+
PciCapability::Vendor(addr) => {
547+
let cap = CapData::read(addr, device.access()).unwrap();
548+
if cap.cfg_type == CapCfgType::Pci {
549+
continue;
579550
}
580-
}
581-
CapCfgType::Notify => {
582-
if notif_cfg.is_none() {
583-
match NotifCfg::new(&pci_cap) {
584-
Some(notif) => notif_cfg = Some(notif),
585-
None => error!(
586-
"Notification config capability of device {device_id:x} could not be used!"
587-
),
551+
let slot = cap.bar;
552+
let Some((addr, size)) = device.memory_map_bar(slot, true) else {
553+
continue;
554+
};
555+
let pci_cap = PciCap {
556+
bar: VirtioPciBar::new(slot, addr.as_u64(), size.try_into().unwrap()),
557+
dev_id: device_id,
558+
cap,
559+
};
560+
match pci_cap.cap.cfg_type {
561+
CapCfgType::Common => {
562+
if com_cfg.is_none() {
563+
match pci_cap.map_common_cfg() {
564+
Some(cap) => com_cfg = Some(ComCfg::new(cap)),
565+
None => error!(
566+
"Common config capability of device {device_id:x} could not be mapped!"
567+
),
568+
}
569+
}
588570
}
589-
}
590-
}
591-
CapCfgType::Isr => {
592-
if isr_cfg.is_none() {
593-
match pci_cap.map_isr_status() {
594-
Some(isr_stat) => isr_cfg = Some(IsrStatus::new(isr_stat)),
595-
None => error!(
596-
"ISR status config capability of device {device_id:x} could not be used!"
597-
),
571+
CapCfgType::Notify => {
572+
if notif_cfg.is_none() {
573+
match NotifCfg::new(&pci_cap) {
574+
Some(notif) => notif_cfg = Some(notif),
575+
None => error!(
576+
"Notification config capability of device {device_id:x} could not be used!"
577+
),
578+
}
579+
}
580+
}
581+
CapCfgType::Isr => {
582+
if isr_cfg.is_none() {
583+
match pci_cap.map_isr_status() {
584+
Some(isr_stat) => isr_cfg = Some(IsrStatus::new(isr_stat)),
585+
None => error!(
586+
"ISR status config capability of device {device_id:x} could not be used!"
587+
),
588+
}
589+
}
598590
}
591+
CapCfgType::SharedMemory => {
592+
let cap_id = pci_cap.cap.id;
593+
error!(
594+
"Shared Memory config capability with id {cap_id} of device {device_id:x} could not be used!"
595+
);
596+
}
597+
CapCfgType::Device => dev_cfg_list.push(pci_cap),
598+
_ => continue,
599599
}
600600
}
601-
CapCfgType::SharedMemory => {
602-
let cap_id = pci_cap.cap.id;
603-
error!(
604-
"Shared Memory config capability with id {cap_id} of device {device_id:x} could not be used!"
601+
#[cfg(feature = "msix")]
602+
PciCapability::MsiX(mut msix_capability) => {
603+
msix_capability.set_enabled(true, device.access());
604+
let (base_addr, _) = device
605+
.memory_map_bar(msix_capability.table_bar(), true)
606+
.unwrap();
607+
let table_ptr = NonNull::slice_from_raw_parts(
608+
NonNull::with_exposed_provenance(
609+
core::num::NonZero::new(
610+
base_addr.as_usize()
611+
+ usize::try_from(msix_capability.table_offset()).unwrap(),
612+
)
613+
.unwrap(),
614+
),
615+
msix_capability.table_size().into(),
605616
);
617+
msix_table = Some(unsafe { VolatileRef::new(table_ptr) });
606618
}
607-
CapCfgType::Device => dev_cfg_list.push(pci_cap),
608-
609-
// PCI's configuration space is allowed to hold other structures, which are not virtio specific and are therefore ignored
619+
// PCI's configuration space is allowed to hold other structures, which are not useful for us and are therefore ignored
610620
// in the following
611621
_ => continue,
612622
}
@@ -617,6 +627,8 @@ pub(crate) fn map_caps(device: &PciDevice<PciConfigRegion>) -> Result<UniCapsCol
617627
notif_cfg: notif_cfg.ok_or(VirtioError::NoNotifCfg(device_id))?,
618628
isr_cfg: isr_cfg.ok_or(VirtioError::NoIsrCfg(device_id))?,
619629
dev_cfg_list,
630+
#[cfg(feature = "msix")]
631+
msix_table,
620632
})
621633
}
622634

@@ -686,9 +698,10 @@ pub(crate) fn init_device(
686698
))]
687699
virtio::Id::Net => match VirtioNetDriver::init(device) {
688700
Ok(virt_net_drv) => {
701+
use crate::drivers::Driver;
689702
info!("Virtio network driver initialized.");
690703

691-
let irq = device.get_irq().unwrap();
704+
let irq = virt_net_drv.get_interrupt_number();
692705
crate::arch::interrupts::add_irq_name(irq, "virtio");
693706
info!("Virtio interrupt handler at line {irq}");
694707

0 commit comments

Comments
 (0)