Skip to content

Commit 938d062

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 ff18234 commit 938d062

File tree

5 files changed

+171
-83
lines changed

5 files changed

+171
-83
lines changed

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
@@ -31,6 +31,8 @@ use self::error::VirtioNetError;
3131
use crate::config::VIRTIO_MAX_QUEUE_SIZE;
3232
use crate::drivers::net::virtio::constants::BUFF_PER_PACKET;
3333
use crate::drivers::net::{NetworkDriver, mtu};
34+
#[cfg(all(feature = "pci", target_arch = "x86_64"))]
35+
use crate::drivers::pci::MsixEntry;
3436
use crate::drivers::virtio::ControlRegisters;
3537
#[cfg(not(feature = "pci"))]
3638
use crate::drivers::virtio::transport::mmio::{ComCfg, IsrStatus, NotifCfg};
@@ -238,11 +240,13 @@ pub(crate) struct VirtioNetDriver<T = Init> {
238240
pub(super) com_cfg: ComCfg,
239241
pub(super) isr_stat: IsrStatus,
240242
pub(super) notif_cfg: NotifCfg,
243+
#[cfg(all(feature = "pci", target_arch = "x86_64"))]
244+
pub(super) msix_table: Option<VolatileRef<'static, [MsixEntry]>>,
241245

242246
pub(super) inner: T,
243247

244248
pub(super) num_vqs: u16,
245-
pub(super) irq: InterruptLine,
249+
pub(super) irq: Option<InterruptLine>,
246250
pub(super) checksums: ChecksumCapabilities,
247251
}
248252

@@ -476,7 +480,7 @@ impl smoltcp::phy::Device for VirtioNetDriver {
476480

477481
impl Driver for VirtioNetDriver<Init> {
478482
fn get_interrupt_number(&self) -> InterruptLine {
479-
self.irq
483+
self.irq.unwrap()
480484
}
481485

482486
fn get_name(&self) -> &'static str {
@@ -732,11 +736,30 @@ impl VirtioNetDriver<Uninit> {
732736
}
733737
debug!("{:?}", self.checksums);
734738

739+
// If self.irq is some, it was filled with a legacy interrupt number which we prefer over MSI-X.
740+
#[cfg(all(feature = "pci", target_arch = "x86_64"))]
741+
if self.irq.is_none()
742+
&& let Some(msix_table) = self.msix_table.as_mut()
743+
{
744+
// Chosen arbitatrily. Ideally does not clash with another interrupt line.
745+
const MSIX_VECTOR: u8 = 112;
746+
let msix_entry = unsafe {
747+
msix_table
748+
.as_mut_ptr()
749+
.map(|table| table.get_unchecked_mut(0))
750+
};
751+
MsixEntry::configure(msix_entry, MSIX_VECTOR);
752+
self.com_cfg.select_vq(0).unwrap().set_msix_table_index(0);
753+
self.irq = Some(MSIX_VECTOR);
754+
};
755+
735756
Ok(VirtioNetDriver {
736757
dev_cfg: self.dev_cfg,
737758
com_cfg: self.com_cfg,
738759
isr_stat: self.isr_stat,
739760
notif_cfg: self.notif_cfg,
761+
#[cfg(all(feature = "pci", target_arch = "x86_64"))]
762+
msix_table: self.msix_table,
740763
inner,
741764
num_vqs: self.num_vqs,
742765
irq: self.irq,

src/drivers/net/virtio/pci.rs

Lines changed: 5 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

@@ -49,9 +51,11 @@ impl VirtioNetDriver<Uninit> {
4951
com_cfg,
5052
isr_stat: isr_cfg,
5153
notif_cfg,
54+
#[cfg(all(feature = "pci", target_arch = "x86_64"))]
55+
msix_table,
5256
inner: Uninit,
5357
num_vqs: 0,
54-
irq: device.get_irq().unwrap(),
58+
irq: device.get_irq(),
5559
checksums: ChecksumCapabilities::default(),
5660
})
5761
}

src/drivers/pci.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,30 @@ pub(crate) fn init() {
540540
});
541541
}
542542

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

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

@@ -561,58 +543,106 @@ pub(crate) fn map_caps(device: &PciDevice<PciConfigRegion>) -> Result<UniCapsCol
561543
return Err(VirtioError::FromPci(PciError::NoCapPtr(device_id)));
562544
}
563545

564-
// Get list of PciCaps pointing to capabilities
565-
let cap_list = match read_caps(device) {
566-
Ok(list) => list,
567-
Err(pci_error) => return Err(VirtioError::FromPci(pci_error)),
568-
};
569-
570546
let mut com_cfg = None;
571547
let mut notif_cfg = None;
572548
let mut isr_cfg = None;
573549
let mut dev_cfg_list = Vec::new();
574-
// Map Caps in virtual memory
575-
for pci_cap in cap_list {
576-
match pci_cap.cap.cfg_type {
577-
CapCfgType::Common => {
578-
if com_cfg.is_none() {
579-
match pci_cap.map_common_cfg() {
580-
Some(cap) => com_cfg = Some(ComCfg::new(cap)),
581-
None => error!(
582-
"Common config capability of device {device_id:x} could not be mapped!"
583-
),
584-
}
550+
#[cfg(all(
551+
feature = "virtio-net",
552+
not(feature = "rtl8139"),
553+
target_arch = "x86_64"
554+
))]
555+
let mut msix_table = None;
556+
557+
// Reads all PCI capabilities, starting at the capabilities list pointer from the
558+
// PCI device.
559+
//
560+
// Maps ONLY Virtio specific capabilities and the MSI-X capability , which allow to locate the actual capability
561+
// structures inside the memory areas, indicated by the BaseAddressRegisters (BAR's).
562+
for capability in device.capabilities().unwrap() {
563+
match capability {
564+
PciCapability::Vendor(addr) => {
565+
let cap = CapData::read(addr, device.access()).unwrap();
566+
if cap.cfg_type == CapCfgType::Pci {
567+
continue;
585568
}
586-
}
587-
CapCfgType::Notify => {
588-
if notif_cfg.is_none() {
589-
match NotifCfg::new(&pci_cap) {
590-
Some(notif) => notif_cfg = Some(notif),
591-
None => error!(
592-
"Notification config capability of device {device_id:x} could not be used!"
593-
),
569+
let slot = cap.bar;
570+
let Some((addr, size)) = device.memory_map_bar(slot, true) else {
571+
continue;
572+
};
573+
let pci_cap = PciCap {
574+
bar: VirtioPciBar::new(slot, addr.as_u64(), size.try_into().unwrap()),
575+
dev_id: device_id,
576+
cap,
577+
};
578+
match pci_cap.cap.cfg_type {
579+
CapCfgType::Common => {
580+
if com_cfg.is_none() {
581+
match pci_cap.map_common_cfg() {
582+
Some(cap) => com_cfg = Some(ComCfg::new(cap)),
583+
None => error!(
584+
"Common config capability of device {device_id:x} could not be mapped!"
585+
),
586+
}
587+
}
594588
}
595-
}
596-
}
597-
CapCfgType::Isr => {
598-
if isr_cfg.is_none() {
599-
match pci_cap.map_isr_status() {
600-
Some(isr_stat) => isr_cfg = Some(IsrStatus::new(isr_stat)),
601-
None => error!(
602-
"ISR status config capability of device {device_id:x} could not be used!"
603-
),
589+
CapCfgType::Notify => {
590+
if notif_cfg.is_none() {
591+
match NotifCfg::new(&pci_cap) {
592+
Some(notif) => notif_cfg = Some(notif),
593+
None => error!(
594+
"Notification config capability of device {device_id:x} could not be used!"
595+
),
596+
}
597+
}
598+
}
599+
CapCfgType::Isr => {
600+
if isr_cfg.is_none() {
601+
match pci_cap.map_isr_status() {
602+
Some(isr_stat) => isr_cfg = Some(IsrStatus::new(isr_stat)),
603+
None => error!(
604+
"ISR status config capability of device {device_id:x} could not be used!"
605+
),
606+
}
607+
}
604608
}
609+
CapCfgType::SharedMemory => {
610+
let cap_id = pci_cap.cap.id;
611+
error!(
612+
"Shared Memory config capability with id {cap_id} of device {device_id:x} could not be used!"
613+
);
614+
}
615+
CapCfgType::Device => dev_cfg_list.push(pci_cap),
616+
_ => continue,
605617
}
606618
}
607-
CapCfgType::SharedMemory => {
608-
let cap_id = pci_cap.cap.id;
609-
error!(
610-
"Shared Memory config capability with id {cap_id} of device {device_id:x} could not be used!"
619+
#[cfg(all(
620+
feature = "virtio-net",
621+
not(feature = "rtl8139"),
622+
target_arch = "x86_64"
623+
))]
624+
PciCapability::MsiX(mut msix_capability) => {
625+
// We prefer legacy interrupts
626+
if device.get_irq().is_some() {
627+
continue;
628+
}
629+
msix_capability.set_enabled(true, device.access());
630+
let (base_addr, _) = device
631+
.memory_map_bar(msix_capability.table_bar(), true)
632+
.unwrap();
633+
let table_ptr = NonNull::slice_from_raw_parts(
634+
NonNull::with_exposed_provenance(
635+
core::num::NonZero::new(
636+
base_addr.as_usize()
637+
+ usize::try_from(msix_capability.table_offset()).unwrap(),
638+
)
639+
.unwrap(),
640+
),
641+
msix_capability.table_size().into(),
611642
);
643+
msix_table = Some(unsafe { VolatileRef::new(table_ptr) });
612644
}
613-
CapCfgType::Device => dev_cfg_list.push(pci_cap),
614-
615-
// PCI's configuration space is allowed to hold other structures, which are not virtio specific and are therefore ignored
645+
// PCI's configuration space is allowed to hold other structures, which are not useful for us and are therefore ignored
616646
// in the following
617647
_ => continue,
618648
}
@@ -623,6 +653,12 @@ pub(crate) fn map_caps(device: &PciDevice<PciConfigRegion>) -> Result<UniCapsCol
623653
notif_cfg: notif_cfg.ok_or(VirtioError::NoNotifCfg(device_id))?,
624654
isr_cfg: isr_cfg.ok_or(VirtioError::NoIsrCfg(device_id))?,
625655
dev_cfg_list,
656+
#[cfg(all(
657+
feature = "virtio-net",
658+
not(feature = "rtl8139"),
659+
target_arch = "x86_64"
660+
))]
661+
msix_table,
626662
})
627663
}
628664

@@ -690,9 +726,10 @@ pub(crate) fn init_device(
690726
))]
691727
virtio::Id::Net => match VirtioNetDriver::init(device) {
692728
Ok(virt_net_drv) => {
729+
use crate::drivers::Driver;
693730
info!("Virtio network driver initialized.");
694731

695-
let irq = device.get_irq().unwrap();
732+
let irq = virt_net_drv.get_interrupt_number();
696733
crate::arch::interrupts::add_irq_name(irq, "virtio");
697734
info!("Virtio interrupt handler at line {irq}");
698735

0 commit comments

Comments
 (0)