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