xref: /aosp_15_r20/external/crosvm/devices/src/virtio/vhost/scmi.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2023 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 use std::collections::BTreeMap;
6 use std::path::Path;
7 
8 use anyhow::anyhow;
9 use anyhow::Context;
10 use base::error;
11 use base::warn;
12 use base::AsRawDescriptor;
13 use base::Event;
14 use base::RawDescriptor;
15 use base::WorkerThread;
16 use vhost::Scmi as VhostScmiHandle;
17 use vhost::Vhost;
18 use vm_memory::GuestMemory;
19 
20 use super::worker::Worker;
21 use super::Error;
22 use super::Result;
23 use crate::virtio::DeviceType;
24 use crate::virtio::Interrupt;
25 use crate::virtio::Queue;
26 use crate::virtio::VirtioDevice;
27 use crate::Suspendable;
28 
29 const QUEUE_SIZE: u16 = 128;
30 const NUM_QUEUES: usize = 2;
31 const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE; NUM_QUEUES];
32 const VIRTIO_SCMI_F_P2A_CHANNELS: u32 = 0;
33 
34 pub struct Scmi {
35     worker_thread: Option<WorkerThread<()>>,
36     vhost_handle: Option<VhostScmiHandle>,
37     interrupts: Option<Vec<Event>>,
38     avail_features: u64,
39     acked_features: u64,
40 }
41 
42 impl Scmi {
43     /// Create a new virtio-scmi device.
new(vhost_scmi_device_path: &Path, base_features: u64) -> Result<Scmi>44     pub fn new(vhost_scmi_device_path: &Path, base_features: u64) -> Result<Scmi> {
45         let handle = VhostScmiHandle::new(vhost_scmi_device_path).map_err(Error::VhostOpen)?;
46 
47         let avail_features = base_features | 1 << VIRTIO_SCMI_F_P2A_CHANNELS;
48 
49         let mut interrupts = Vec::new();
50         for _ in 0..NUM_QUEUES {
51             interrupts.push(Event::new().map_err(Error::VhostIrqCreate)?);
52         }
53 
54         Ok(Scmi {
55             worker_thread: None,
56             vhost_handle: Some(handle),
57             interrupts: Some(interrupts),
58             avail_features,
59             acked_features: 0,
60         })
61     }
62 
acked_features(&self) -> u6463     pub fn acked_features(&self) -> u64 {
64         self.acked_features
65     }
66 }
67 
68 impl VirtioDevice for Scmi {
keep_rds(&self) -> Vec<RawDescriptor>69     fn keep_rds(&self) -> Vec<RawDescriptor> {
70         let mut keep_rds = Vec::new();
71 
72         if let Some(handle) = &self.vhost_handle {
73             keep_rds.push(handle.as_raw_descriptor());
74         }
75 
76         if let Some(interrupt) = &self.interrupts {
77             for vhost_int in interrupt.iter() {
78                 keep_rds.push(vhost_int.as_raw_descriptor());
79             }
80         }
81 
82         keep_rds
83     }
84 
device_type(&self) -> DeviceType85     fn device_type(&self) -> DeviceType {
86         DeviceType::Scmi
87     }
88 
queue_max_sizes(&self) -> &[u16]89     fn queue_max_sizes(&self) -> &[u16] {
90         QUEUE_SIZES
91     }
92 
features(&self) -> u6493     fn features(&self) -> u64 {
94         self.avail_features
95     }
96 
ack_features(&mut self, value: u64)97     fn ack_features(&mut self, value: u64) {
98         let mut v = value;
99 
100         // Check if the guest is ACK'ing a feature that we didn't claim to have.
101         let unrequested_features = v & !self.avail_features;
102         if unrequested_features != 0 {
103             warn!("scmi: virtio-scmi got unknown feature ack: {:x}", v);
104 
105             // Don't count these features as acked.
106             v &= !unrequested_features;
107         }
108         self.acked_features |= v;
109     }
110 
activate( &mut self, mem: GuestMemory, interrupt: Interrupt, queues: BTreeMap<usize, Queue>, ) -> anyhow::Result<()>111     fn activate(
112         &mut self,
113         mem: GuestMemory,
114         interrupt: Interrupt,
115         queues: BTreeMap<usize, Queue>,
116     ) -> anyhow::Result<()> {
117         if queues.len() != NUM_QUEUES {
118             return Err(anyhow!(
119                 "net: expected {} queues, got {}",
120                 NUM_QUEUES,
121                 queues.len()
122             ));
123         }
124         let vhost_handle = self.vhost_handle.take().context("missing vhost_handle")?;
125         let interrupts = self.interrupts.take().context("missing interrupts")?;
126         let acked_features = self.acked_features;
127         let mut worker = Worker::new(
128             queues,
129             vhost_handle,
130             interrupts,
131             interrupt,
132             acked_features,
133             None,
134         );
135         let activate_vqs = |_handle: &VhostScmiHandle| -> Result<()> { Ok(()) };
136 
137         worker
138             .init(mem, QUEUE_SIZES, activate_vqs, None)
139             .context("vhost worker init exited with error")?;
140 
141         self.worker_thread = Some(WorkerThread::start("vhost_scmi", move |kill_evt| {
142             let cleanup_vqs = |_handle: &VhostScmiHandle| -> Result<()> { Ok(()) };
143             let result = worker.run(cleanup_vqs, kill_evt);
144             if let Err(e) = result {
145                 error!("vhost_scmi worker thread exited with error: {:?}", e);
146             }
147         }));
148         Ok(())
149     }
150 
on_device_sandboxed(&mut self)151     fn on_device_sandboxed(&mut self) {
152         // ignore the error but to log the error. We don't need to do
153         // anything here because when activate, the other vhost set up
154         // will be failed to stop the activate thread.
155         if let Some(vhost_handle) = &self.vhost_handle {
156             match vhost_handle.set_owner() {
157                 Ok(_) => {}
158                 Err(e) => error!("{}: failed to set owner: {:?}", self.debug_label(), e),
159             }
160         }
161     }
162 }
163 
164 impl Suspendable for Scmi {}
165