mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
The virtio spec says the used-ring length is bytes the device wrote to device writable descriptors. The net TX descriptors are device readable only (the device wrote nothing back) so the length needs to be 0. On the ctrl queue the number of bytes reported was wrongly the size of the status descriptor not the number of bytes written (the descriptor is permitted to be larger). Signed-off-by: Rob Bradford <rbradford@meta.com>
194 lines
7.4 KiB
Rust
194 lines
7.4 KiB
Rust
// Copyright (c) 2021 Intel Corporation. All rights reserved.
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
|
|
use log::{debug, error, info, warn};
|
|
use thiserror::Error;
|
|
use virtio_bindings::virtio_net::{
|
|
VIRTIO_NET_CTRL_ANNOUNCE, VIRTIO_NET_CTRL_ANNOUNCE_ACK, VIRTIO_NET_CTRL_GUEST_OFFLOADS,
|
|
VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET, VIRTIO_NET_CTRL_MQ, VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX,
|
|
VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN, VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET, VIRTIO_NET_CTRL_RX,
|
|
VIRTIO_NET_CTRL_RX_ALLMULTI, VIRTIO_NET_CTRL_RX_ALLUNI, VIRTIO_NET_CTRL_RX_NOBCAST,
|
|
VIRTIO_NET_CTRL_RX_NOMULTI, VIRTIO_NET_CTRL_RX_NOUNI, VIRTIO_NET_CTRL_RX_PROMISC,
|
|
VIRTIO_NET_CTRL_VLAN, VIRTIO_NET_CTRL_VLAN_ADD, VIRTIO_NET_CTRL_VLAN_DEL, VIRTIO_NET_ERR,
|
|
VIRTIO_NET_OK,
|
|
};
|
|
use virtio_queue::{Queue, QueueT};
|
|
use vm_memory::{ByteValued, Bytes, GuestMemoryError};
|
|
use vm_virtio::{AccessPlatform, Translatable};
|
|
|
|
use super::virtio_features_to_tap_offload;
|
|
use crate::{GuestMemoryMmap, Tap};
|
|
|
|
#[derive(Error, Debug)]
|
|
pub enum Error {
|
|
/// Read queue failed.
|
|
#[error("Read queue failed")]
|
|
GuestMemory(#[source] GuestMemoryError),
|
|
/// No control header descriptor
|
|
#[error("No control header descriptor")]
|
|
NoControlHeaderDescriptor,
|
|
/// Missing the data descriptor in the chain.
|
|
#[error("Missing the data descriptor in the chain")]
|
|
NoDataDescriptor,
|
|
/// No status descriptor
|
|
#[error("No status descriptor")]
|
|
NoStatusDescriptor,
|
|
/// Failed adding used index
|
|
#[error("Failed adding used index")]
|
|
QueueAddUsed(#[source] virtio_queue::Error),
|
|
/// Failed creating an iterator over the queue
|
|
#[error("Failed creating an iterator over the queue")]
|
|
QueueIterator(#[source] virtio_queue::Error),
|
|
/// Failed enabling notification for the queue
|
|
#[error("Failed enabling notification for the queue")]
|
|
QueueEnableNotification(#[source] virtio_queue::Error),
|
|
}
|
|
|
|
type Result<T> = std::result::Result<T, Error>;
|
|
|
|
#[repr(C, packed)]
|
|
#[derive(Debug, Clone, Copy, Default)]
|
|
pub struct ControlHeader {
|
|
pub class: u8,
|
|
pub cmd: u8,
|
|
}
|
|
|
|
// SAFETY: ControlHeader only contains a series of integers
|
|
unsafe impl ByteValued for ControlHeader {}
|
|
|
|
fn is_tolerated_ctrl_command(ctrl_hdr: ControlHeader) -> bool {
|
|
match u32::from(ctrl_hdr.class) {
|
|
VIRTIO_NET_CTRL_RX => matches!(
|
|
u32::from(ctrl_hdr.cmd),
|
|
VIRTIO_NET_CTRL_RX_PROMISC
|
|
| VIRTIO_NET_CTRL_RX_ALLMULTI
|
|
| VIRTIO_NET_CTRL_RX_ALLUNI
|
|
| VIRTIO_NET_CTRL_RX_NOMULTI
|
|
| VIRTIO_NET_CTRL_RX_NOUNI
|
|
| VIRTIO_NET_CTRL_RX_NOBCAST
|
|
),
|
|
VIRTIO_NET_CTRL_VLAN => matches!(
|
|
u32::from(ctrl_hdr.cmd),
|
|
VIRTIO_NET_CTRL_VLAN_ADD | VIRTIO_NET_CTRL_VLAN_DEL
|
|
),
|
|
VIRTIO_NET_CTRL_ANNOUNCE => u32::from(ctrl_hdr.cmd) == VIRTIO_NET_CTRL_ANNOUNCE_ACK,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
pub struct CtrlQueue {
|
|
pub taps: Vec<Tap>,
|
|
}
|
|
|
|
impl CtrlQueue {
|
|
pub fn new(taps: Vec<Tap>) -> Self {
|
|
CtrlQueue { taps }
|
|
}
|
|
|
|
pub fn process(
|
|
&mut self,
|
|
mem: &GuestMemoryMmap,
|
|
queue: &mut Queue,
|
|
access_platform: Option<&dyn AccessPlatform>,
|
|
) -> Result<()> {
|
|
while let Some(mut desc_chain) = queue.pop_descriptor_chain(mem) {
|
|
let ctrl_desc = desc_chain.next().ok_or(Error::NoControlHeaderDescriptor)?;
|
|
|
|
let ctrl_hdr: ControlHeader = desc_chain
|
|
.memory()
|
|
.read_obj(
|
|
ctrl_desc
|
|
.addr()
|
|
.translate_gva(access_platform, ctrl_desc.len() as usize)
|
|
.map_err(|e| Error::GuestMemory(GuestMemoryError::IOError(e)))?,
|
|
)
|
|
.map_err(Error::GuestMemory)?;
|
|
let data_desc = desc_chain.next().ok_or(Error::NoDataDescriptor)?;
|
|
|
|
let data_desc_addr = data_desc
|
|
.addr()
|
|
.translate_gva(access_platform, data_desc.len() as usize)
|
|
.map_err(|e| Error::GuestMemory(GuestMemoryError::IOError(e)))?;
|
|
|
|
let status_desc = desc_chain.next().ok_or(Error::NoStatusDescriptor)?;
|
|
|
|
let ok = match u32::from(ctrl_hdr.class) {
|
|
VIRTIO_NET_CTRL_MQ => {
|
|
let queue_pairs = desc_chain
|
|
.memory()
|
|
.read_obj::<u16>(data_desc_addr)
|
|
.map_err(Error::GuestMemory)?;
|
|
if u32::from(ctrl_hdr.cmd) != VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET {
|
|
warn!("Unsupported command: {}", ctrl_hdr.cmd);
|
|
false
|
|
} else if (queue_pairs < VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN as u16)
|
|
|| (queue_pairs > VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX as u16)
|
|
{
|
|
warn!("Number of MQ pairs out of range: {queue_pairs}");
|
|
false
|
|
} else {
|
|
info!("Number of MQ pairs requested: {queue_pairs}");
|
|
true
|
|
}
|
|
}
|
|
VIRTIO_NET_CTRL_GUEST_OFFLOADS => {
|
|
let features = desc_chain
|
|
.memory()
|
|
.read_obj::<u64>(data_desc_addr)
|
|
.map_err(Error::GuestMemory)?;
|
|
if u32::from(ctrl_hdr.cmd) == VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET {
|
|
let mut ok = true;
|
|
for tap in self.taps.iter_mut() {
|
|
info!("Reprogramming tap offload with features: {features}");
|
|
tap.set_offload(virtio_features_to_tap_offload(features))
|
|
.map_err(|e| {
|
|
error!("Error programming tap offload: {e:?}");
|
|
ok = false;
|
|
})
|
|
.ok();
|
|
}
|
|
ok
|
|
} else {
|
|
warn!("Unsupported command: {}", ctrl_hdr.cmd);
|
|
false
|
|
}
|
|
}
|
|
_ if is_tolerated_ctrl_command(ctrl_hdr) => {
|
|
debug!("Ignoring unsupported but tolerated control command {ctrl_hdr:?}");
|
|
true
|
|
}
|
|
_ => {
|
|
warn!("Unsupported command {ctrl_hdr:?}");
|
|
false
|
|
}
|
|
};
|
|
|
|
desc_chain
|
|
.memory()
|
|
.write_obj(
|
|
if ok { VIRTIO_NET_OK } else { VIRTIO_NET_ERR } as u8,
|
|
status_desc
|
|
.addr()
|
|
.translate_gva(access_platform, status_desc.len() as usize)
|
|
.map_err(|e| Error::GuestMemory(GuestMemoryError::IOError(e)))?,
|
|
)
|
|
.map_err(Error::GuestMemory)?;
|
|
// Per the virtio spec the used length is bytes the device wrote
|
|
// to device-writable descriptors; here just the 1-byte ack.
|
|
queue
|
|
.add_used(desc_chain.memory(), desc_chain.head_index(), 1)
|
|
.map_err(Error::QueueAddUsed)?;
|
|
|
|
if !queue
|
|
.enable_notification(mem)
|
|
.map_err(Error::QueueEnableNotification)?
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|