blob: 1c7c2d8c03c22a7d6a6af09c4e78ec57aeb1bcc5 [file] [log] [blame] [edit]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::collections::BTreeMap;
use std::path::Path;
use anyhow::anyhow;
use anyhow::Context;
use base::error;
use base::warn;
use base::AsRawDescriptor;
use base::Event;
use base::RawDescriptor;
use base::WorkerThread;
use vhost::Scmi as VhostScmiHandle;
use vhost::Vhost;
use vm_memory::GuestMemory;
use super::worker::Worker;
use super::Error;
use super::Result;
use crate::virtio::DeviceType;
use crate::virtio::Interrupt;
use crate::virtio::Queue;
use crate::virtio::VirtioDevice;
use crate::Suspendable;
const QUEUE_SIZE: u16 = 128;
const NUM_QUEUES: usize = 2;
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE; NUM_QUEUES];
const VIRTIO_SCMI_F_P2A_CHANNELS: u32 = 0;
pub struct Scmi {
worker_thread: Option<WorkerThread<()>>,
vhost_handle: Option<VhostScmiHandle>,
interrupts: Option<Vec<Event>>,
avail_features: u64,
acked_features: u64,
}
impl Scmi {
/// Create a new virtio-scmi device.
pub fn new(vhost_scmi_device_path: &Path, base_features: u64) -> Result<Scmi> {
let handle = VhostScmiHandle::new(vhost_scmi_device_path).map_err(Error::VhostOpen)?;
let avail_features = base_features | 1 << VIRTIO_SCMI_F_P2A_CHANNELS;
let mut interrupts = Vec::new();
for _ in 0..NUM_QUEUES {
interrupts.push(Event::new().map_err(Error::VhostIrqCreate)?);
}
Ok(Scmi {
worker_thread: None,
vhost_handle: Some(handle),
interrupts: Some(interrupts),
avail_features,
acked_features: 0,
})
}
pub fn acked_features(&self) -> u64 {
self.acked_features
}
}
impl VirtioDevice for Scmi {
fn keep_rds(&self) -> Vec<RawDescriptor> {
let mut keep_rds = Vec::new();
if let Some(handle) = &self.vhost_handle {
keep_rds.push(handle.as_raw_descriptor());
}
if let Some(interrupt) = &self.interrupts {
for vhost_int in interrupt.iter() {
keep_rds.push(vhost_int.as_raw_descriptor());
}
}
keep_rds
}
fn device_type(&self) -> DeviceType {
DeviceType::Scmi
}
fn queue_max_sizes(&self) -> &[u16] {
QUEUE_SIZES
}
fn features(&self) -> u64 {
self.avail_features
}
fn ack_features(&mut self, value: u64) {
let mut v = value;
// Check if the guest is ACK'ing a feature that we didn't claim to have.
let unrequested_features = v & !self.avail_features;
if unrequested_features != 0 {
warn!("scmi: virtio-scmi got unknown feature ack: {:x}", v);
// Don't count these features as acked.
v &= !unrequested_features;
}
self.acked_features |= v;
}
fn activate(
&mut self,
mem: GuestMemory,
interrupt: Interrupt,
queues: BTreeMap<usize, Queue>,
) -> anyhow::Result<()> {
if queues.len() != NUM_QUEUES {
return Err(anyhow!(
"net: expected {} queues, got {}",
NUM_QUEUES,
queues.len()
));
}
let vhost_handle = self.vhost_handle.take().context("missing vhost_handle")?;
let interrupts = self.interrupts.take().context("missing interrupts")?;
let acked_features = self.acked_features;
let mut worker = Worker::new(
queues,
vhost_handle,
interrupts,
interrupt,
acked_features,
None,
);
let activate_vqs = |_handle: &VhostScmiHandle| -> Result<()> { Ok(()) };
worker
.init(mem, QUEUE_SIZES, activate_vqs, None)
.context("vhost worker init exited with error")?;
self.worker_thread = Some(WorkerThread::start("vhost_scmi", move |kill_evt| {
let cleanup_vqs = |_handle: &VhostScmiHandle| -> Result<()> { Ok(()) };
let result = worker.run(cleanup_vqs, kill_evt);
if let Err(e) = result {
error!("vhost_scmi worker thread exited with error: {:?}", e);
}
}));
Ok(())
}
fn on_device_sandboxed(&mut self) {
// ignore the error but to log the error. We don't need to do
// anything here because when activate, the other vhost set up
// will be failed to stop the activate thread.
if let Some(vhost_handle) = &self.vhost_handle {
match vhost_handle.set_owner() {
Ok(_) => {}
Err(e) => error!("{}: failed to set owner: {:?}", self.debug_label(), e),
}
}
}
}
impl Suspendable for Scmi {}