Skip to content

Commit 019e89f

Browse files
committed
feature(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 used as a fallback.
1 parent ef27b79 commit 019e89f

5 files changed

Lines changed: 179 additions & 83 deletions

File tree

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: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ use self::error::VirtioNetError;
3232
use crate::config::VIRTIO_MAX_QUEUE_SIZE;
3333
use crate::drivers::net::virtio::constants::BUFF_PER_PACKET;
3434
use crate::drivers::net::{NetworkDriver, mtu};
35+
#[cfg(all(feature = "pci", target_arch = "x86_64"))]
36+
use crate::drivers::pci::MsixEntry;
3537
use crate::drivers::virtio::ControlRegisters;
3638
#[cfg(not(feature = "pci"))]
3739
use crate::drivers::virtio::transport::mmio::{ComCfg, IsrStatus, NotifCfg};
@@ -239,11 +241,13 @@ pub(crate) struct VirtioNetDriver<T = Init> {
239241
pub(super) com_cfg: ComCfg,
240242
pub(super) isr_stat: IsrStatus,
241243
pub(super) notif_cfg: NotifCfg,
244+
#[cfg(all(feature = "pci", target_arch = "x86_64"))]
245+
pub(super) msix_table: Option<VolatileRef<'static, [MsixEntry]>>,
242246

243247
pub(super) inner: T,
244248

245249
pub(super) num_vqs: u16,
246-
pub(super) irq: InterruptLine,
250+
pub(super) irq: Option<InterruptLine>,
247251
pub(super) checksums: ChecksumCapabilities,
248252
}
249253

@@ -481,7 +485,7 @@ impl smoltcp::phy::Device for VirtioNetDriver {
481485

482486
impl Driver for VirtioNetDriver<Init> {
483487
fn get_interrupt_number(&self) -> InterruptLine {
484-
self.irq
488+
self.irq.unwrap()
485489
}
486490

487491
fn get_name(&self) -> &'static str {
@@ -737,11 +741,33 @@ impl VirtioNetDriver<Uninit> {
737741
}
738742
debug!("{:?}", self.checksums);
739743

744+
// If self.irq is some, it was filled with a legacy interrupt number which we prefer over MSI-X.
745+
#[cfg(all(feature = "pci", target_arch = "x86_64"))]
746+
if self.irq.is_none()
747+
&& let Some(msix_table) = self.msix_table.as_mut()
748+
{
749+
warn!(
750+
"Setting up message signaled interrupts as a fallback. MSI-X is known to not work on all platforms (e.g. QEMU with TAP)."
751+
);
752+
// Chosen arbitatrily. Ideally does not clash with another interrupt line.
753+
const MSIX_VECTOR: u8 = 112;
754+
let msix_entry = unsafe {
755+
msix_table
756+
.as_mut_ptr()
757+
.map(|table| table.get_unchecked_mut(0))
758+
};
759+
MsixEntry::configure(msix_entry, MSIX_VECTOR);
760+
self.com_cfg.select_vq(0).unwrap().set_msix_table_index(0);
761+
self.irq = Some(MSIX_VECTOR);
762+
};
763+
740764
Ok(VirtioNetDriver {
741765
dev_cfg: self.dev_cfg,
742766
com_cfg: self.com_cfg,
743767
isr_stat: self.isr_stat,
744768
notif_cfg: self.notif_cfg,
769+
#[cfg(all(feature = "pci", target_arch = "x86_64"))]
770+
msix_table: self.msix_table,
745771
inner,
746772
num_vqs: self.num_vqs,
747773
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(all(feature = "pci", target_arch = "x86_64"))]
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(all(feature = "pci", target_arch = "x86_64"))]
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: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,30 @@ pub(crate) fn init() {
537537
});
538538
}
539539

540+
#[repr(C)]
541+
pub(crate) struct MsixEntry {
542+
addr_low: u32,
543+
addr_high: u32,
544+
data: u32,
545+
control: u32,
546+
}
547+
548+
impl MsixEntry {
549+
#[cfg(target_arch = "x86_64")]
550+
pub fn configure(msix_entry: volatile::VolatilePtr<'_, Self>, vector: u8) {
551+
use bit_field::BitField;
552+
use volatile::map_field;
553+
554+
// Mask the entry because "[s]oftware must not modify the Address, Data, or Steering Tag fields
555+
// of an entry while it is unmasked." (PCIe spec. 6.1.4.2)
556+
map_field!(msix_entry.control).update(|mut control| *control.set_bit(0, true));
557+
558+
map_field!(msix_entry.addr_low).update(|mut addr_low| *addr_low.set_bits(20..32, 0xfee));
559+
map_field!(msix_entry.data).update(|mut data| *data.set_bits(0..8, u32::from(vector) + 32));
560+
map_field!(msix_entry.control).update(|mut control| *control.set_bit(0, false));
561+
}
562+
}
563+
540564
/// A module containing PCI specific errors
541565
///
542566
/// Errors include...

src/drivers/virtio/transport/pci.rs

Lines changed: 116 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,12 @@ 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(all(
185+
feature = "virtio-net",
186+
not(feature = "rtl8139"),
187+
target_arch = "x86_64"
188+
))]
189+
pub(crate) msix_table: Option<VolatileRef<'static, [crate::drivers::pci::MsixEntry]>>,
184190
}
185191
/// Wraps a [`CommonCfg`] in order to preserve
186192
/// the original structure.
@@ -256,6 +262,19 @@ impl VqCfgHandler<'_> {
256262
.write(addr.as_u64().into());
257263
}
258264

265+
#[cfg(all(
266+
feature = "virtio-net",
267+
not(feature = "rtl8139"),
268+
target_arch = "x86_64"
269+
))]
270+
pub fn set_msix_table_index(&mut self, index: u16) {
271+
self.select_queue();
272+
self.raw
273+
.as_mut_ptr()
274+
.queue_msix_vector()
275+
.write(index.into());
276+
}
277+
259278
pub fn notif_off(&mut self) -> u16 {
260279
self.select_queue();
261280
self.raw.as_mut_ptr().queue_notify_off().read().to_ne()
@@ -509,43 +528,6 @@ impl PciBar {
509528
}
510529
}
511530

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-
549531
pub(crate) fn map_caps(device: &PciDevice<PciConfigRegion>) -> Result<UniCapsColl, VirtioError> {
550532
let device_id = device.device_id();
551533

@@ -555,58 +537,106 @@ pub(crate) fn map_caps(device: &PciDevice<PciConfigRegion>) -> Result<UniCapsCol
555537
return Err(VirtioError::FromPci(PciError::NoCapPtr(device_id)));
556538
}
557539

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-
564540
let mut com_cfg = None;
565541
let mut notif_cfg = None;
566542
let mut isr_cfg = None;
567543
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-
}
544+
#[cfg(all(
545+
feature = "virtio-net",
546+
not(feature = "rtl8139"),
547+
target_arch = "x86_64"
548+
))]
549+
let mut msix_table = None;
550+
551+
// Reads all PCI capabilities, starting at the capabilities list pointer from the
552+
// PCI device.
553+
//
554+
// Maps ONLY Virtio specific capabilities and the MSI-X capability , which allow to locate the actual capability
555+
// structures inside the memory areas, indicated by the BaseAddressRegisters (BAR's).
556+
for capability in device.capabilities().unwrap() {
557+
match capability {
558+
PciCapability::Vendor(addr) => {
559+
let cap = CapData::read(addr, device.access()).unwrap();
560+
if cap.cfg_type == CapCfgType::Pci {
561+
continue;
579562
}
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-
),
563+
let slot = cap.bar;
564+
let Some((addr, size)) = device.memory_map_bar(slot, true) else {
565+
continue;
566+
};
567+
let pci_cap = PciCap {
568+
bar: VirtioPciBar::new(slot, addr.as_u64(), size.try_into().unwrap()),
569+
dev_id: device_id,
570+
cap,
571+
};
572+
match pci_cap.cap.cfg_type {
573+
CapCfgType::Common => {
574+
if com_cfg.is_none() {
575+
match pci_cap.map_common_cfg() {
576+
Some(cap) => com_cfg = Some(ComCfg::new(cap)),
577+
None => error!(
578+
"Common config capability of device {device_id:x} could not be mapped!"
579+
),
580+
}
581+
}
588582
}
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-
),
583+
CapCfgType::Notify => {
584+
if notif_cfg.is_none() {
585+
match NotifCfg::new(&pci_cap) {
586+
Some(notif) => notif_cfg = Some(notif),
587+
None => error!(
588+
"Notification config capability of device {device_id:x} could not be used!"
589+
),
590+
}
591+
}
592+
}
593+
CapCfgType::Isr => {
594+
if isr_cfg.is_none() {
595+
match pci_cap.map_isr_status() {
596+
Some(isr_stat) => isr_cfg = Some(IsrStatus::new(isr_stat)),
597+
None => error!(
598+
"ISR status config capability of device {device_id:x} could not be used!"
599+
),
600+
}
601+
}
598602
}
603+
CapCfgType::SharedMemory => {
604+
let cap_id = pci_cap.cap.id;
605+
error!(
606+
"Shared Memory config capability with id {cap_id} of device {device_id:x} could not be used!"
607+
);
608+
}
609+
CapCfgType::Device => dev_cfg_list.push(pci_cap),
610+
_ => continue,
599611
}
600612
}
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!"
613+
#[cfg(all(
614+
feature = "virtio-net",
615+
not(feature = "rtl8139"),
616+
target_arch = "x86_64"
617+
))]
618+
PciCapability::MsiX(mut msix_capability) => {
619+
// We prefer legacy interrupts
620+
if device.get_irq().is_some() {
621+
continue;
622+
}
623+
msix_capability.set_enabled(true, device.access());
624+
let (base_addr, _) = device
625+
.memory_map_bar(msix_capability.table_bar(), true)
626+
.unwrap();
627+
let table_ptr = NonNull::slice_from_raw_parts(
628+
NonNull::with_exposed_provenance(
629+
core::num::NonZero::new(
630+
base_addr.as_usize()
631+
+ usize::try_from(msix_capability.table_offset()).unwrap(),
632+
)
633+
.unwrap(),
634+
),
635+
msix_capability.table_size().into(),
605636
);
637+
msix_table = Some(unsafe { VolatileRef::new(table_ptr) });
606638
}
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
639+
// PCI's configuration space is allowed to hold other structures, which are not useful for us and are therefore ignored
610640
// in the following
611641
_ => continue,
612642
}
@@ -617,6 +647,12 @@ pub(crate) fn map_caps(device: &PciDevice<PciConfigRegion>) -> Result<UniCapsCol
617647
notif_cfg: notif_cfg.ok_or(VirtioError::NoNotifCfg(device_id))?,
618648
isr_cfg: isr_cfg.ok_or(VirtioError::NoIsrCfg(device_id))?,
619649
dev_cfg_list,
650+
#[cfg(all(
651+
feature = "virtio-net",
652+
not(feature = "rtl8139"),
653+
target_arch = "x86_64"
654+
))]
655+
msix_table,
620656
})
621657
}
622658

@@ -686,9 +722,10 @@ pub(crate) fn init_device(
686722
))]
687723
virtio::Id::Net => match VirtioNetDriver::init(device) {
688724
Ok(virt_net_drv) => {
725+
use crate::drivers::Driver;
689726
info!("Virtio network driver initialized.");
690727

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

0 commit comments

Comments
 (0)