Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 2 additions & 0 deletions lib/propolis/src/hw/virtio/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,4 +256,6 @@ mod probes {
fn virtio_viona_mq_set_use_pairs(cause: u8, npairs: u16) {}

fn virtio_device_needs_reset() {}
fn virtio_set_status(value: u8) {}
fn virtio_state_reset() {}
}
54 changes: 46 additions & 8 deletions lib/propolis/src/hw/virtio/pci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -658,11 +658,11 @@ impl PciVirtioState {
state.queue_select = wo.read_u16();
}
CommonConfigReg::QueueSize => {
let state = self.state.lock().unwrap();
let mut state = self.state.lock().unwrap();
match VqSize::try_from(wo.read_u16()) {
Err(_) => {
// Bad queue size.
self.set_needs_reset(dev);
self.needs_reset_locked(dev, &mut state);
}
Ok(offered) => {
let qs = state.queue_select;
Expand Down Expand Up @@ -1051,21 +1051,53 @@ impl PciVirtioState {
}

fn set_status(&self, dev: &dyn VirtioDevice, value: u8) {
probes::virtio_set_status!(|| value);
let mut state = self.state.lock().unwrap();
let status = Status::from_bits_truncate(value);
if status == Status::RESET && state.status != Status::RESET {
self.virtio_reset(dev, state);
} else {
// XXX: better device status FSM
state.status = status;
if status.contains(Status::FEATURES_OK) {
Comment thread
iximeow marked this conversation as resolved.
if dev.set_features(state.negotiated_features).is_err() {
self.needs_reset_locked(dev, &mut state);
}
if self.apply_status(dev, &mut state, status).is_err() {
self.needs_reset_locked(dev, &mut state);
}
}
}

fn apply_status(
&self,
dev: &dyn VirtioDevice,
state: &mut MutexGuard<VirtioState>,
status: Status,
) -> Result<(), ()> {
if status == state.status {
// No actual difference, bail out early.
return Ok(());
}

if !status.contains(state.status) {
// The driver has disregarded VirtIO 1.2 section 2.1.2:
//
// > The driver MUST NOT clear a device status bit.
//
// If we allowed such a thing then the guest might toggle
// FEATURES_OK and violate the expectation that `set_features`
// is called only once when setting up a device. Instead, the
// guest driver is in the wrong and we'll set NEEDS_RESET.
return Err(());
}

// Any bits here are being set at most once since the last device reset.
let new_bits = status.difference(state.status);

if new_bits.contains(Status::FEATURES_OK) {
dev.set_features(state.negotiated_features)?;
}

state.status = status;

Ok(())
Comment thread
iximeow marked this conversation as resolved.
Outdated
}

/// Set the "Needs Reset" state on the VirtIO device
fn needs_reset_locked(
&self,
Expand Down Expand Up @@ -1130,6 +1162,7 @@ impl PciVirtioState {
dev: &dyn VirtioDevice,
mut state: MutexGuard<VirtioState>,
) {
probes::virtio_state_reset!(|| ());
self.reset_queues_locked(dev, &mut state);
state.reset();
let _ = self.isr_state.read_clear();
Expand Down Expand Up @@ -1514,6 +1547,11 @@ const LEGACY_REG_QUEUE_NOTIFY_OFFSET: usize = 0x10;
const COMMON_REG_OFFSET: usize = 0;
const COMMON_REG_SIZE: usize =
4 + 4 + 4 + 4 + 2 + 2 + 1 + 1 + 2 + 2 + 2 + 2 + 2 + 8 + 8 + 8 + 2 + 2;
// Some tests want to poke at the common config registers, but before doing so use the total
// common_cfg struct size to spot-check that the layout is correct. Ideally the register map we
// build here could go both ways and either be public, or public for tests.
#[cfg(test)]
pub const COMMON_REG_SIZE_TEST: usize = COMMON_REG_SIZE;
const DEVICE_REG_OFFSET: usize = PAGE_SIZE;
const NOTIFY_REG_OFFSET: usize = 2 * PAGE_SIZE;
pub const NOTIFY_REG_SIZE: usize = 4;
Expand Down
9 changes: 5 additions & 4 deletions lib/propolis/src/hw/virtio/queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1015,10 +1015,11 @@ impl VirtQueues {
pub fn get(&self, qid: u16) -> Option<&Arc<VirtQueue>> {
let len = self.len();
let qid = usize::from(qid);
// XXX: This special case is for viona, which always puts the
// control queue at the end of queue vector. None of the other
// devices currently handle queues specially in this way, but we
// should come up with some better mechanism here.
// XXX: This special case is for the virtio network device, which always
// puts the control queue at the end of queue vector (see VirtIO 1.2
// section 5.1.2). None of the other devices currently handle queues
// specially in this way, but we should come up with some better
// mechanism here.
if qid + 1 == len {
Some(self.get_control())
} else {
Expand Down
Loading
Loading