Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions .github/buildomat/jobs/check-headers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ set -e

GATE_REF="$(./tools/check_headers gate_ref)"

# TODO: `--branch` is overly restrictive, but it's what we've got. In git 2.49
# the --revision flag was added to `git-clone`, and can clone an arbitrary
# revision, which is more appropriate here. We might be tracking an arbitrary
# commit with some changes in illumos-gate that isn't yet merged, after all.
git clone --depth 1 --branch "$GATE_REF" \
https://code.oxide.computer/illumos-gate ./gate_src
# `git clone --branch` only accepts branches and tags, but GATE_REF may be an
# arbitrary ref (for example, a Gerrit change ref for an illumos-gate change
# that has not yet merged). An init-and-fetch handles any ref the remote
# advertises.
git init ./gate_src
git -C ./gate_src fetch --depth 1 \
https://code.oxide.computer/illumos-gate "$GATE_REF"
git -C ./gate_src checkout --detach FETCH_HEAD

./tools/check_headers run ./gate_src
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ dlpi = { git = "https://github.com/oxidecomputer/dlpi-sys", branch = "main" }
ispf = { git = "https://github.com/oxidecomputer/ispf" }
libloading = "0.7"
p9ds = { git = "https://github.com/oxidecomputer/p9fs" }
softnpu = { git = "https://github.com/oxidecomputer/softnpu" }
softnpu = { git = "https://github.com/oxidecomputer/softnpu", branch = "zl/multicast" }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling this out to reset to main before merge.


# Omicron-related
internal-dns-resolver = { git = "https://github.com/oxidecomputer/omicron", rev = "5fd53a9c9ff2b0e47bfef3fe842b7516877b0162" }
Expand Down Expand Up @@ -149,7 +149,7 @@ newtype-uuid = { version = "1.0.1", features = [ "v4" ] }
# "feature" for `utsname`, "poll" for `PollFlags`
nix = { version = "0.31", features = [ "feature", "poll" ] }
owo-colors = "4"
oxide-tokio-rt = "0.1.2"
oxide-tokio-rt = "0.1.4"
paste = "1.0.15"
pin-project-lite = "0.2.13"
proc-macro2 = "1.0"
Expand Down
46 changes: 45 additions & 1 deletion crates/viona-api/src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub const VNA_IOC_GET_MTU: i32 = vna_ioc(0x27);
pub const VNA_IOC_SET_MTU: i32 = vna_ioc(0x28);
pub const VNA_IOC_SET_NOTIFY_MMIO: i32 = vna_ioc(0x29);
pub const VNA_IOC_INTR_POLL_MQ: i32 = vna_ioc(0x2a);
pub const VNA_IOC_SET_MAC_FILTERS: i32 = vna_ioc(0x2b);

/// VirtIO 1.2 queue pair support.
pub const VNA_IOC_GET_PAIRS: i32 = vna_ioc(0x30);
Expand Down Expand Up @@ -135,6 +136,49 @@ pub const VIONA_PROMISC_ALL: i32 = 2;
#[cfg(feature = "falcon")]
pub const VIONA_PROMISC_ALL_VLAN: i32 = 3;

/// Number of bytes in a filterable MAC address.
pub const VIONA_MAC_FILTER_ADDRL: usize = 6;

/// Maximum number of unicast MAC address filters accepted by viona.
///
/// No cross-device convention exists for a separate unicast table. QEMU
/// shares one 64-entry table across both address classes (see
/// [`VIONA_MAX_MCAST_FILTERS`]). Half the multicast table is generous for
/// entries that are rejected unless they match the primary MAC.
pub const VIONA_MAX_UNICAST_FILTERS: usize = 32;

/// Maximum number of multicast MAC address filters accepted by viona.
///
/// Matches the 64-entry table convention of other virtio-net devices, per
/// QEMU's [`MAC_TABLE_ENTRIES`].
///
/// [`MAC_TABLE_ENTRIES`]: https://github.com/qemu/qemu/blob/f893c46c3931b3684d235d221bf8b7844ddbf1d7/include/hw/virtio/virtio-net.h
pub const VIONA_MAX_MCAST_FILTERS: usize = 64;

/// Complete unicast and multicast MAC filter tables, replacing any tables
/// previously installed on the link. Counts of zero clear all filters.
///
/// Viona installs only multicast filters on the underlying MAC client. The
/// unicast table exists for shape parity with `VIRTIO_NET_CTRL_MAC_TABLE_SET`,
/// and entries other than the primary MAC of the link are rejected.
#[repr(C)]
pub struct vioc_mac_filters {
pub vmf_nucast: u32,
pub vmf_nmcast: u32,
pub vmf_ucast: [[u8; VIONA_MAC_FILTER_ADDRL]; VIONA_MAX_UNICAST_FILTERS],
pub vmf_mcast: [[u8; VIONA_MAC_FILTER_ADDRL]; VIONA_MAX_MCAST_FILTERS],
}
impl Default for vioc_mac_filters {
fn default() -> Self {
Self {
vmf_nucast: 0,
vmf_nmcast: 0,
vmf_ucast: [[0; VIONA_MAC_FILTER_ADDRL]; VIONA_MAX_UNICAST_FILTERS],
vmf_mcast: [[0; VIONA_MAC_FILTER_ADDRL]; VIONA_MAX_MCAST_FILTERS],
}
}
}

#[repr(C)]
#[derive(Default)]
pub struct vioc_get_params {
Expand All @@ -154,7 +198,7 @@ pub struct vioc_set_params {
/// This is the viona interface version which viona_api expects to operate
/// against. All constants and structs defined by the crate are done so in
/// terms of that specific version.
pub const VIONA_CURRENT_INTERFACE_VERSION: u32 = 6;
pub const VIONA_CURRENT_INTERFACE_VERSION: u32 = 7;

/// Maximum size of packed nvlists used in viona parameter ioctls
pub const VIONA_MAX_PARAM_NVLIST_SZ: usize = 4096;
6 changes: 5 additions & 1 deletion crates/viona-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ fn minor(meta: &std::fs::Metadata) -> u32 {
#[repr(u32)]
#[derive(Copy, Clone)]
pub enum ApiVersion {
/// Adds support for installing MAC address filter tables on the
/// underlying MAC client.
V7 = 7,

/// Adds multi-queue support and change the data structure for per-queue
/// interrupt polling to a compact bitmap.
V6 = 6,
Expand All @@ -218,7 +222,7 @@ pub enum ApiVersion {
}
impl ApiVersion {
pub const fn current() -> Self {
Self::V6
Self::V7
}
}
impl PartialEq<ApiVersion> for u32 {
Expand Down
10 changes: 10 additions & 0 deletions lib/propolis/src/hw/virtio/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,16 @@ pub trait VirtioDevice: Send + Sync + 'static + Lifecycle {
) -> Result<(), ()> {
Ok(())
}

/// Notification that the virtio portion of the device has been reset.
///
/// This fires for guest-initiated status resets as well as full device
/// resets, after the generic virtio state and virtqueues have been reset.
///
/// Devices should use this to restore any state tied to feature
/// negotiation, since the next driver may negotiate a different feature
/// set.
fn device_reset(&self) {}
Comment on lines +230 to +239

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is already covered by Lifecycle::reset()?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite as Lifecycle::reset() covers a full device reset, but a guest writing 0 to the virtio status register calls set_status() -> virtio_reset() without invoking Lifecycle::reset(). That path is used for driver unload/reload and must clear the state negotiated by the previous driver.

The existing notification is only queue_change(VqChange::Reset), called once per queue while the virtio state lock is held. device_reset(), on the other hand, provides one device-level callback after the generic state and queues are reset and the lock is released.

}

pub trait VirtioIntr: Send + 'static {
Expand Down
4 changes: 4 additions & 0 deletions lib/propolis/src/hw/virtio/pci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,10 @@ impl PciVirtioState {
self.reset_queues_locked(dev, &mut state);
state.reset();
let _ = self.isr_state.read_clear();
// Release the state lock before calling into the device so that its
// reset handling may query generic virtio state.
drop(state);
dev.device_reset();
}

pub fn reset<D>(&self, dev: &D)
Expand Down
88 changes: 53 additions & 35 deletions lib/propolis/src/hw/virtio/softnpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,27 +348,14 @@ impl PciVirtioSoftNpuPort {

let pkt = packet_in::new(&frame[..n]);

let mut pipeline = match self.pipeline.lock() {
Ok(pipe) => pipe,
Err(e) => {
error!(self.log, "failed to lock pipeline: {}", e);
break;
}
};
let pl: &mut Box<dyn Pipeline> = match &mut *pipeline {
Some(ref mut x) => &mut x.1,
None => {
// This just means no P4 program has been set by the guest.
break;
}
};

PacketHandler::process_guest_packet(
if !PacketHandler::process_guest_packet(
pkt,
&self.data_handles,
pl,
&self.pipeline,
&self.log,
);
) {
break;
}
}

if push_used {
Expand Down Expand Up @@ -504,21 +491,13 @@ impl PacketHandler {
//
// process packet with loaded P4 program
//

// TODO pipeline should not need to be mutable for packet handling?
let pkt = packet_in::new(&msg[..n]);
let mut p = pipeline.lock().unwrap();
let pl = match &mut *p {
Some(ref mut pl) => &mut pl.1,
None => continue, // no program is loaded
};

Self::process_external_packet(
index + 1,
pkt,
&data_handles,
&virtio,
pl,
&pipeline,
&log,
)
}
Expand All @@ -531,12 +510,26 @@ impl PacketHandler {
mut pkt: packet_in<'_>,
data_handles: &Vec<dlpi::DlpiHandle>,
virtio: &Arc<PortVirtioState>,
pipeline: &mut Box<dyn Pipeline>,
pipeline: &Mutex<Option<LoadedP4Program>>,
log: &Logger,
) {
for (mut out_pkt, port) in
// We hold the pipeline lock only while the pipeline computes the
// egress set. Every port worker serializes on this lock, and the
// dlpi and virtio egress I/O can block. Holding the lock across
// that I/O stalls the other workers, which is especially costly
// for multicast where one input frame fans out to several ports.
// The egress packets borrow from `pkt` rather than the pipeline,
// so they remain valid after the lock is released.
let outputs = {
let mut guard = pipeline.lock().unwrap();
let pipeline = match &mut *guard {
Some(ref mut loaded) => &mut loaded.1,
None => return, // no program is loaded
};
pipeline.process_packet(index as u16, &mut pkt)
{
};

for (mut out_pkt, port) in outputs {
// packet is going to CPU port
if port == 0 {
Self::send_packet_to_cpu_port(&mut out_pkt, virtio, &log);
Expand All @@ -555,20 +548,44 @@ impl PacketHandler {

/// Run a packet coming into the ASIC from the guest pci port through the
/// loaded pipeline and forward it on to its destination.
///
/// Returns false if no P4 program is loaded or the pipeline lock is
/// poisoned, in which case the caller should stop reading frames.
fn process_guest_packet(
mut pkt: packet_in<'_>,
data_handles: &Vec<dlpi::DlpiHandle>,
pipeline: &mut Box<dyn Pipeline>,
pipeline: &Mutex<Option<LoadedP4Program>>,
log: &Logger,
) {
for (mut out_pkt, port) in pipeline.process_packet(0, &mut pkt) {
) -> bool {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I'd rather this return a Result<(), E> for some local enum E, just because it makes the semantics at the caller more obvious than needing to know what true/false represent here (i.e., if let Err(e) = process_guest_pkt(..) { break; }).

// We hold the pipeline lock only for pipeline processing, not the
// egress I/O (see `process_external_packet` for rationale).
let outputs = {
let mut guard = match pipeline.lock() {
Ok(guard) => guard,
Err(e) => {
error!(log, "failed to lock pipeline: {e}");
return false;
}
};
let pipeline = match &mut *guard {
Some(ref mut loaded) => &mut loaded.1,
None => {
// This just means no P4 program has been set by the
// guest.
return false;
}
};
pipeline.process_packet(0, &mut pkt)
};

for (mut out_pkt, port) in outputs {
if port == 0 {
// no looping packets back to the guest
return;
return true;
}
if port == SOFTNPU_CPU_AUX_PORT {
// we are not currently emulating this port type
return;
return true;
}
Self::send_packet_to_ext_port(
&mut out_pkt,
Expand All @@ -577,6 +594,7 @@ impl PacketHandler {
&log,
);
}
true
}

/// Send a packet out an external port using dlpi.
Expand Down
Loading
Loading