Skip to content

Commit fa5c1a8

Browse files
pfmooneyiximeow
andauthored
Wire up viona notify fast path (#754)
The viona driver has the ability to hook an ioport (as specified by the userspace vmm) in order to more efficiently process notifications from the guest that virtqueues have new available entries. This cuts down on the number of VM exits which must be processed by userspace. --------- Co-authored-by: iximeow <iximeow@oxide.computer>
1 parent 30d32c4 commit fa5c1a8

File tree

7 files changed

+304
-56
lines changed

7 files changed

+304
-56
lines changed

crates/viona-api/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ impl VionaFd {
124124
Ok(minor(&meta))
125125
}
126126

127-
/// Check VMM ioctl command against those known to not require any
127+
/// Check viona ioctl command against those known to not require any
128128
/// copyin/copyout to function.
129129
const fn ioctl_usize_safe(cmd: i32) -> bool {
130130
matches!(
@@ -135,9 +135,10 @@ impl VionaFd {
135135
| ioctls::VNA_IOC_RING_PAUSE
136136
| ioctls::VNA_IOC_RING_INTR_CLR
137137
| ioctls::VNA_IOC_VERSION
138+
| ioctls::VNA_IOC_SET_NOTIFY_IOP
138139
| ioctls::VNA_IOC_SET_PROMISC
139140
| ioctls::VNA_IOC_GET_MTU
140-
| ioctls::VNA_IOC_SET_MTU
141+
| ioctls::VNA_IOC_SET_MTU,
141142
)
142143
}
143144
}

lib/propolis/src/hw/pci/bar.rs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -121,33 +121,35 @@ impl Bars {
121121
&mut self,
122122
bar: BarN,
123123
val: u32,
124-
) -> Option<(BarDefine, u64, u64)> {
124+
) -> Option<WriteResult> {
125125
let idx = bar as usize;
126126
let ent = &mut self.entries[idx];
127-
let (def, old, new) = match ent.kind {
127+
let (id, def, val_old, val_new) = match ent.kind {
128128
EntryKind::Empty => return None,
129129
EntryKind::Pio(size) => {
130130
let mask = u32::from(!(size - 1));
131131
let old = ent.value;
132132
ent.value = u64::from(val & mask);
133-
(BarDefine::Pio(size), old, ent.value)
133+
(bar, BarDefine::Pio(size), old, ent.value)
134134
}
135135
EntryKind::Mmio(size) => {
136136
let mask = !(size - 1);
137137
let old = ent.value;
138138
ent.value = u64::from(val & mask);
139-
(BarDefine::Mmio(size), old, ent.value)
139+
(bar, BarDefine::Mmio(size), old, ent.value)
140140
}
141141
EntryKind::Mmio64(size) => {
142142
let old = ent.value;
143143
let mask = !(size - 1) as u32;
144144
let low = val & mask;
145145
ent.value = (old & (0xffffffff << 32)) | u64::from(low);
146-
(BarDefine::Mmio64(size), old, ent.value)
146+
(bar, BarDefine::Mmio64(size), old, ent.value)
147147
}
148148
EntryKind::Mmio64High => {
149149
assert!(idx > 0);
150-
let ent = &mut self.entries[idx - 1];
150+
let real_idx = idx - 1;
151+
let id = BarN::from_repr(real_idx as u8).unwrap();
152+
let ent = &mut self.entries[real_idx];
151153
let size = match ent.kind {
152154
EntryKind::Mmio64(sz) => sz,
153155
_ => panic!(),
@@ -156,11 +158,11 @@ impl Bars {
156158
let old = ent.value;
157159
let high = ((u64::from(val) << 32) & mask) & 0xffffffff00000000;
158160
ent.value = high | (old & 0xffffffff);
159-
(BarDefine::Mmio64(size), old, ent.value)
161+
(id, BarDefine::Mmio64(size), old, ent.value)
160162
}
161163
};
162-
if old != new {
163-
return Some((def, old, new));
164+
if val_old != val_new {
165+
return Some(WriteResult { id, def, val_old, val_new });
164166
}
165167
None
166168
}
@@ -287,6 +289,18 @@ impl Bars {
287289
}
288290
}
289291

292+
/// Result from a write to a BAR
293+
pub struct WriteResult {
294+
/// Identifier of the actual impacted BAR.
295+
///
296+
/// If write was to the high word of a 64-bit BAR, this would hold the
297+
/// `BarN` for the lower word.
298+
pub id: BarN,
299+
pub def: BarDefine,
300+
pub val_old: u64,
301+
pub val_new: u64,
302+
}
303+
290304
pub mod migrate {
291305
use serde::{Deserialize, Serialize};
292306

lib/propolis/src/hw/pci/device.rs

Lines changed: 67 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ use crate::migrate::*;
1515
use crate::util::regmap::{Flags, RegMap};
1616

1717
use lazy_static::lazy_static;
18+
use strum::IntoEnumIterator;
1819

1920
pub trait Device: Send + Sync + 'static {
2021
fn device_state(&self) -> &DeviceState;
2122

22-
#[allow(unused_variables)]
2323
fn bar_rw(&self, bar: BarN, rwo: RWOp) {
2424
match rwo {
2525
RWOp::Read(ro) => {
@@ -30,7 +30,6 @@ pub trait Device: Send + Sync + 'static {
3030
}
3131
}
3232
}
33-
#[allow(unused_variables)]
3433
fn cfg_rw(&self, region: u8, rwo: RWOp) {
3534
match rwo {
3635
RWOp::Read(ro) => {
@@ -46,6 +45,13 @@ pub trait Device: Send + Sync + 'static {
4645
fn interrupt_mode_change(&self, mode: IntrMode) {}
4746
#[allow(unused_variables)]
4847
fn msi_update(&self, info: MsiUpdate) {}
48+
49+
/// Notification that configuration of BAR(s) has changed, either due to
50+
/// writes to the BARs themselves, or an overall status change (via the
51+
/// Command register or a device reset).
52+
#[allow(unused_variables)]
53+
fn bar_update(&self, bstate: BarState) {}
54+
4955
// TODO
5056
// fn cap_read(&self);
5157
// fn cap_write(&self);
@@ -181,6 +187,18 @@ impl State {
181187
fn attached(&self) -> &bus::Attachment {
182188
self.attach.as_ref().unwrap()
183189
}
190+
/// Is MMIO access decoding enabled?
191+
fn mmio_en(&self) -> bool {
192+
self.reg_command.contains(RegCmd::MMIO_EN)
193+
}
194+
/// Is PIO access decoding enabled?
195+
fn pio_en(&self) -> bool {
196+
self.reg_command.contains(RegCmd::IO_EN)
197+
}
198+
/// Given the device state, is decoding enabled for a specified [BarDefine]
199+
fn decoding_active(&self, bar: &BarDefine) -> bool {
200+
(bar.is_pio() && self.pio_en()) || (bar.is_mmio() && self.mmio_en())
201+
}
184202
}
185203

186204
pub(super) struct Cap {
@@ -375,15 +393,17 @@ impl DeviceState {
375393
StdCfgReg::Bar(bar) => {
376394
let val = wo.read_u32();
377395
let mut state = self.state.lock().unwrap();
378-
if let Some((def, _old, new)) = state.bars.reg_write(*bar, val)
379-
{
380-
let pio_en = state.reg_command.contains(RegCmd::IO_EN);
381-
let mmio_en = state.reg_command.contains(RegCmd::MMIO_EN);
382-
396+
if let Some(res) = state.bars.reg_write(*bar, val) {
383397
let attach = state.attached();
384-
if (pio_en && def.is_pio()) || (mmio_en && def.is_mmio()) {
385-
attach.bar_unregister(*bar);
386-
attach.bar_register(*bar, def, new);
398+
if state.decoding_active(&res.def) {
399+
attach.bar_unregister(res.id);
400+
attach.bar_register(res.id, res.def, res.val_new);
401+
dev.bar_update(BarState {
402+
id: res.id,
403+
def: res.def,
404+
value: res.val_new,
405+
decode_en: true,
406+
});
387407
}
388408
}
389409
}
@@ -425,6 +445,8 @@ impl DeviceState {
425445

426446
// Update BAR registrations
427447
if diff.intersects(RegCmd::IO_EN | RegCmd::MMIO_EN) {
448+
let pio_en = val.contains(RegCmd::IO_EN);
449+
let mmio_en = val.contains(RegCmd::MMIO_EN);
428450
for n in BarN::iter() {
429451
let bar = state.bars.get(n);
430452
if bar.is_none() {
@@ -433,18 +455,30 @@ impl DeviceState {
433455
let (def, v) = bar.unwrap();
434456

435457
if diff.contains(RegCmd::IO_EN) && def.is_pio() {
436-
if val.contains(RegCmd::IO_EN) {
458+
if pio_en {
437459
attach.bar_register(n, def, v);
438460
} else {
439461
attach.bar_unregister(n);
440462
}
463+
dev.bar_update(BarState {
464+
id: n,
465+
def,
466+
value: v,
467+
decode_en: pio_en,
468+
});
441469
}
442470
if diff.contains(RegCmd::MMIO_EN) && def.is_mmio() {
443-
if val.contains(RegCmd::MMIO_EN) {
471+
if mmio_en {
444472
attach.bar_register(n, def, v);
445473
} else {
446474
attach.bar_unregister(n);
447475
}
476+
dev.bar_update(BarState {
477+
id: n,
478+
def,
479+
value: v,
480+
decode_en: mmio_en,
481+
});
448482
}
449483
}
450484
}
@@ -482,6 +516,17 @@ impl DeviceState {
482516
self.which_intr_mode(&state)
483517
}
484518

519+
pub(crate) fn bar(&self, id: BarN) -> Option<BarState> {
520+
let state = self.state.lock().unwrap();
521+
state.bars.get(id).map(|(def, value)| {
522+
let decode_en = match def {
523+
BarDefine::Pio(_) => state.pio_en(),
524+
BarDefine::Mmio(_) | BarDefine::Mmio64(_) => state.mmio_en(),
525+
};
526+
BarState { id, def, value, decode_en }
527+
})
528+
}
529+
485530
fn cfg_cap_rw(&self, dev: &dyn Device, id: &CfgReg, rwo: RWOp) {
486531
match id {
487532
CfgReg::CapId(i) => {
@@ -608,10 +653,7 @@ impl DeviceState {
608653
let attach = inner.attached();
609654
for n in BarN::iter() {
610655
if let Some((def, addr)) = inner.bars.get(n) {
611-
let pio_en = inner.reg_command.contains(RegCmd::IO_EN);
612-
let mmio_en = inner.reg_command.contains(RegCmd::MMIO_EN);
613-
614-
if (pio_en && def.is_pio()) || (mmio_en && def.is_mmio()) {
656+
if inner.decoding_active(&def) {
615657
attach.bar_register(n, def, addr);
616658
}
617659
}
@@ -1093,6 +1135,15 @@ impl Clone for MsixHdl {
10931135
}
10941136
}
10951137

1138+
/// Describes the state of a BAR
1139+
pub struct BarState {
1140+
pub id: BarN,
1141+
pub def: BarDefine,
1142+
pub value: u64,
1143+
/// Is decoding for this BAR enabled in the device control?
1144+
pub decode_en: bool,
1145+
}
1146+
10961147
pub struct Builder {
10971148
ident: Ident,
10981149
lintr_support: bool,

lib/propolis/src/hw/pci/mod.rs

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use std::sync::{Arc, Mutex};
1111
use crate::common::*;
1212
use crate::intr_pins::IntrPin;
1313

14-
use strum::FromRepr;
14+
use strum::{EnumIter, FromRepr};
1515

1616
pub mod bar;
1717
pub mod bits;
@@ -273,7 +273,9 @@ impl Display for Bdf {
273273
}
274274
}
275275

276-
#[derive(Copy, Clone, Eq, PartialEq, Debug, Ord, PartialOrd, FromRepr)]
276+
#[derive(
277+
Copy, Clone, Eq, PartialEq, Debug, Ord, PartialOrd, FromRepr, EnumIter,
278+
)]
277279
#[repr(u8)]
278280
pub enum BarN {
279281
BAR0 = 0,
@@ -283,23 +285,6 @@ pub enum BarN {
283285
BAR4,
284286
BAR5,
285287
}
286-
impl BarN {
287-
fn iter() -> BarIter {
288-
BarIter { n: 0 }
289-
}
290-
}
291-
struct BarIter {
292-
n: u8,
293-
}
294-
impl Iterator for BarIter {
295-
type Item = BarN;
296-
297-
fn next(&mut self) -> Option<Self::Item> {
298-
let res = BarN::from_repr(self.n)?;
299-
self.n += 1;
300-
Some(res)
301-
}
302-
}
303288

304289
#[repr(u8)]
305290
#[derive(Copy, Clone)]

lib/propolis/src/hw/virtio/pci.rs

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

55
use std::ffi::c_void;
6+
use std::num::NonZeroU16;
67
use std::sync::atomic::{AtomicBool, Ordering};
78
use std::sync::{Arc, Condvar, Mutex, MutexGuard, Weak};
89

@@ -88,6 +89,36 @@ impl VirtioState {
8889
pub trait PciVirtio: VirtioDevice + Send + Sync + 'static {
8990
fn virtio_state(&self) -> &PciVirtioState;
9091
fn pci_state(&self) -> &pci::DeviceState;
92+
93+
#[allow(unused_variables)]
94+
/// Notification that the IO port representing the queue notification
95+
/// register in the device BAR has changed.
96+
fn notify_port_update(&self, state: Option<NonZeroU16>) {}
97+
98+
/// Notification from the PCI emulation that one of the BARs has undergone a
99+
/// change of configuration
100+
fn bar_update(&self, bstate: pci::BarState) {
101+
if bstate.id == pci::BarN::BAR0 {
102+
// Notify the device about the location (if any) of the Queue Notify
103+
// register in the containing BAR region.
104+
let port = if bstate.decode_en {
105+
// Having registered `bstate.value` as the address in BAR0 only
106+
// succeeds if that address through to the size of the
107+
// registered region - the virtio legacy config registers - does
108+
// not wrap. The base address *could* be zero, unwise as that
109+
// would be, but adding LEGACY_REG_OFF_QUEUE_NOTIFY guarantees
110+
// that the computed offset here is non-zero.
111+
let notify_port_addr = NonZeroU16::new(
112+
bstate.value as u16 + LEGACY_REG_OFF_QUEUE_NOTIFY as u16,
113+
)
114+
.expect("addition does not wrap");
115+
Some(notify_port_addr)
116+
} else {
117+
None
118+
};
119+
self.notify_port_update(port);
120+
}
121+
}
91122
}
92123

93124
impl<D: PciVirtio + Send + Sync + 'static> pci::Device for D {
@@ -163,6 +194,10 @@ impl<D: PciVirtio + Send + Sync + 'static> pci::Device for D {
163194
state.intr_mode_updating = false;
164195
vs.state_cv.notify_all();
165196
}
197+
198+
fn bar_update(&self, bstate: pci::BarState) {
199+
PciVirtio::bar_update(self, bstate);
200+
}
166201
}
167202

168203
pub struct PciVirtioState {
@@ -428,9 +463,9 @@ impl PciVirtioState {
428463

429464
/// Indicate to the guest that the VirtIO device has encountered an error of
430465
/// some sort and requires a reset.
431-
pub fn set_needs_reset(&self, _dev: &dyn VirtioDevice) {
466+
pub fn set_needs_reset(&self, dev: &dyn VirtioDevice) {
432467
let mut state = self.state.lock().unwrap();
433-
self.needs_reset_locked(_dev, &mut state);
468+
self.needs_reset_locked(dev, &mut state);
434469
}
435470

436471
fn queue_notify(&self, dev: &dyn VirtioDevice, queue: u16) {
@@ -624,6 +659,12 @@ impl MigrateMulti for dyn PciVirtio {
624659
// to the VirtIO state.
625660
vs.set_intr_mode(ps, ps.get_intr_mode().into(), true);
626661

662+
// Perform a (potentially spurious) update notification for the BAR
663+
// containing the virtio registers. This ensures that anything
664+
// interested in the placement of that BAR (such as the notify-port
665+
// logic) is kept well aware
666+
self.bar_update(ps.bar(pci::BarN::BAR0).unwrap());
667+
627668
Ok(())
628669
}
629670
}
@@ -796,6 +837,7 @@ enum VirtioTop {
796837

797838
const LEGACY_REG_SZ: usize = 0x18;
798839
const LEGACY_REG_SZ_NO_MSIX: usize = 0x14;
840+
const LEGACY_REG_OFF_QUEUE_NOTIFY: usize = 0x10;
799841

800842
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
801843
enum LegacyReg {

0 commit comments

Comments
 (0)