• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 The Chromium OS Authors. All rights reserved.
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::cell::RefCell;
6 use std::fs::OpenOptions;
7 use std::rc::Rc;
8 use std::sync::{atomic::AtomicU64, atomic::Ordering, Arc};
9 
10 use anyhow::{anyhow, bail, Context};
11 use argh::FromArgs;
12 use futures::future::{AbortHandle, Abortable};
13 use sync::Mutex;
14 use vmm_vhost::message::*;
15 
16 use base::{warn, Event, Timer};
17 use cros_async::{sync::Mutex as AsyncMutex, EventAsync, Executor, TimerAsync};
18 use data_model::DataInit;
19 use disk::create_async_disk_file;
20 use hypervisor::ProtectionType;
21 use vm_memory::GuestMemory;
22 
23 use crate::virtio::block::asynchronous::{flush_disk, handle_queue};
24 use crate::virtio::block::*;
25 use crate::virtio::vhost::user::device::{
26     handler::{DeviceRequestHandler, Doorbell, VhostUserBackend},
27     vvu::pci::VvuPciDevice,
28 };
29 use crate::virtio::{self, base_features, block::sys::*, copy_config};
30 
31 const QUEUE_SIZE: u16 = 256;
32 const NUM_QUEUES: u16 = 16;
33 
34 pub(crate) struct BlockBackend {
35     ex: Executor,
36     disk_state: Rc<AsyncMutex<DiskState>>,
37     disk_size: Arc<AtomicU64>,
38     block_size: u32,
39     seg_max: u32,
40     avail_features: u64,
41     acked_features: u64,
42     acked_protocol_features: VhostUserProtocolFeatures,
43     flush_timer: Rc<RefCell<TimerAsync>>,
44     flush_timer_armed: Rc<RefCell<bool>>,
45     workers: [Option<AbortHandle>; Self::MAX_QUEUE_NUM],
46 }
47 
48 impl BlockBackend {
49     /// Creates a new block backend.
50     ///
51     /// * `ex`: executor used to run this device task.
52     /// * `filename`: Name of the disk image file.
53     /// * `options`: Vector of file options.
54     ///   - `read-only`
new(ex: &Executor, filename: &str, options: Vec<&str>) -> anyhow::Result<Self>55     pub(crate) fn new(ex: &Executor, filename: &str, options: Vec<&str>) -> anyhow::Result<Self> {
56         let read_only = options.contains(&"read-only");
57         let sparse = false;
58         let block_size = 512;
59         let f = OpenOptions::new()
60             .read(true)
61             .write(!read_only)
62             .create(false)
63             .open(filename)
64             .context("Failed to open disk file")?;
65         let disk_image = create_async_disk_file(f).context("Failed to create async file")?;
66 
67         let base_features = base_features(ProtectionType::Unprotected);
68 
69         if block_size % SECTOR_SIZE as u32 != 0 {
70             bail!(
71                 "Block size {} is not a multiple of {}.",
72                 block_size,
73                 SECTOR_SIZE,
74             );
75         }
76         let disk_size = disk_image.get_len()?;
77         if disk_size % block_size as u64 != 0 {
78             warn!(
79                 "Disk size {} is not a multiple of block size {}; \
80                  the remainder will not be visible to the guest.",
81                 disk_size, block_size,
82             );
83         }
84 
85         let avail_features = build_avail_features(base_features, read_only, sparse, true)
86             | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
87 
88         let seg_max = get_seg_max(QUEUE_SIZE);
89 
90         let async_image = disk_image.to_async_disk(ex)?;
91 
92         let disk_size = Arc::new(AtomicU64::new(disk_size));
93 
94         let disk_state = Rc::new(AsyncMutex::new(DiskState::new(
95             async_image,
96             Arc::clone(&disk_size),
97             read_only,
98             sparse,
99             None, // id: Option<BlockId>,
100         )));
101 
102         let timer = Timer::new().context("Failed to create a timer")?;
103         let flush_timer_write = Rc::new(RefCell::new(
104             TimerAsync::new(
105                 // Call try_clone() to share the same underlying FD with the `flush_disk` task.
106                 timer.0.try_clone().context("Failed to clone flush_timer")?,
107                 ex,
108             )
109             .context("Failed to create an async timer")?,
110         ));
111         // Create a separate TimerAsync with the same backing kernel timer. This allows the
112         // `flush_disk` task to borrow its copy waiting for events while the queue handlers can
113         // still borrow their copy momentarily to set timeouts.
114         // Call try_clone() to share the same underlying FD with the `flush_disk` task.
115         let flush_timer_read = timer
116             .0
117             .try_clone()
118             .context("Failed to clone flush_timer")
119             .and_then(|t| TimerAsync::new(t, ex).context("Failed to create an async timer"))?;
120         let flush_timer_armed = Rc::new(RefCell::new(false));
121         ex.spawn_local(flush_disk(
122             Rc::clone(&disk_state),
123             flush_timer_read,
124             Rc::clone(&flush_timer_armed),
125         ))
126         .detach();
127 
128         Ok(BlockBackend {
129             ex: ex.clone(),
130             disk_state,
131             disk_size,
132             block_size,
133             seg_max,
134             avail_features,
135             acked_features: 0,
136             acked_protocol_features: VhostUserProtocolFeatures::empty(),
137             flush_timer: flush_timer_write,
138             flush_timer_armed,
139             workers: Default::default(),
140         })
141     }
142 }
143 
144 impl VhostUserBackend for BlockBackend {
145     const MAX_QUEUE_NUM: usize = NUM_QUEUES as usize;
146     const MAX_VRING_LEN: u16 = QUEUE_SIZE;
147 
148     type Error = anyhow::Error;
149 
features(&self) -> u64150     fn features(&self) -> u64 {
151         self.avail_features
152     }
153 
ack_features(&mut self, value: u64) -> anyhow::Result<()>154     fn ack_features(&mut self, value: u64) -> anyhow::Result<()> {
155         let unrequested_features = value & !self.avail_features;
156         if unrequested_features != 0 {
157             bail!("invalid features are given: {:#x}", unrequested_features);
158         }
159 
160         self.acked_features |= value;
161 
162         Ok(())
163     }
164 
acked_features(&self) -> u64165     fn acked_features(&self) -> u64 {
166         self.acked_features
167     }
168 
protocol_features(&self) -> VhostUserProtocolFeatures169     fn protocol_features(&self) -> VhostUserProtocolFeatures {
170         VhostUserProtocolFeatures::CONFIG | VhostUserProtocolFeatures::MQ
171     }
172 
ack_protocol_features(&mut self, features: u64) -> anyhow::Result<()>173     fn ack_protocol_features(&mut self, features: u64) -> anyhow::Result<()> {
174         let features = VhostUserProtocolFeatures::from_bits(features)
175             .ok_or_else(|| anyhow!("invalid protocol features are given: {:#x}", features))?;
176         let supported = self.protocol_features();
177         self.acked_protocol_features = features & supported;
178         Ok(())
179     }
180 
acked_protocol_features(&self) -> u64181     fn acked_protocol_features(&self) -> u64 {
182         self.acked_protocol_features.bits()
183     }
184 
read_config(&self, offset: u64, data: &mut [u8])185     fn read_config(&self, offset: u64, data: &mut [u8]) {
186         let config_space = {
187             let disk_size = self.disk_size.load(Ordering::Relaxed);
188             build_config_space(disk_size, self.seg_max, self.block_size, NUM_QUEUES)
189         };
190         copy_config(data, 0, config_space.as_slice(), offset);
191     }
192 
reset(&mut self)193     fn reset(&mut self) {
194         panic!("Unsupported call to reset");
195     }
196 
start_queue( &mut self, idx: usize, mut queue: virtio::Queue, mem: GuestMemory, doorbell: Arc<Mutex<Doorbell>>, kick_evt: Event, ) -> anyhow::Result<()>197     fn start_queue(
198         &mut self,
199         idx: usize,
200         mut queue: virtio::Queue,
201         mem: GuestMemory,
202         doorbell: Arc<Mutex<Doorbell>>,
203         kick_evt: Event,
204     ) -> anyhow::Result<()> {
205         if let Some(handle) = self.workers.get_mut(idx).and_then(Option::take) {
206             warn!("Starting new queue handler without stopping old handler");
207             handle.abort();
208         }
209 
210         // Enable any virtqueue features that were negotiated (like VIRTIO_RING_F_EVENT_IDX).
211         queue.ack_features(self.acked_features);
212 
213         let kick_evt = EventAsync::new(kick_evt.0, &self.ex)
214             .context("failed to create EventAsync for kick_evt")?;
215         let (handle, registration) = AbortHandle::new_pair();
216 
217         let disk_state = Rc::clone(&self.disk_state);
218         let timer = Rc::clone(&self.flush_timer);
219         let timer_armed = Rc::clone(&self.flush_timer_armed);
220         self.ex
221             .spawn_local(Abortable::new(
222                 handle_queue(
223                     self.ex.clone(),
224                     mem,
225                     disk_state,
226                     Rc::new(RefCell::new(queue)),
227                     kick_evt,
228                     doorbell,
229                     timer,
230                     timer_armed,
231                 ),
232                 registration,
233             ))
234             .detach();
235 
236         self.workers[idx] = Some(handle);
237         Ok(())
238     }
239 
stop_queue(&mut self, idx: usize)240     fn stop_queue(&mut self, idx: usize) {
241         if let Some(handle) = self.workers.get_mut(idx).and_then(Option::take) {
242             handle.abort();
243         }
244     }
245 }
246 
247 #[derive(FromArgs)]
248 #[argh(description = "")]
249 struct Options {
250     #[argh(
251         option,
252         description = "path and options of the disk file.",
253         arg_name = "PATH<:read-only>"
254     )]
255     file: String,
256     #[argh(option, description = "path to a vhost-user socket", arg_name = "PATH")]
257     socket: Option<String>,
258     #[argh(
259         option,
260         description = "VFIO-PCI device name (e.g. '0000:00:07.0')",
261         arg_name = "STRING"
262     )]
263     vfio: Option<String>,
264 }
265 
266 /// Starts a vhost-user block device.
267 /// Returns an error if the given `args` is invalid or the device fails to run.
run_block_device(program_name: &str, args: &[&str]) -> anyhow::Result<()>268 pub fn run_block_device(program_name: &str, args: &[&str]) -> anyhow::Result<()> {
269     let opts = match Options::from_args(&[program_name], args) {
270         Ok(opts) => opts,
271         Err(e) => {
272             if e.status.is_err() {
273                 bail!(e.output);
274             } else {
275                 println!("{}", e.output);
276             }
277             return Ok(());
278         }
279     };
280 
281     if !(opts.socket.is_some() ^ opts.vfio.is_some()) {
282         bail!("Exactly one of `--socket` or `--vfio` is required");
283     }
284 
285     let ex = Executor::new().context("failed to create executor")?;
286 
287     let mut fileopts = opts.file.split(":").collect::<Vec<_>>();
288     let filename = fileopts.remove(0);
289 
290     let block = BlockBackend::new(&ex, filename, fileopts)?;
291     let handler = DeviceRequestHandler::new(block);
292     match (opts.socket, opts.vfio) {
293         (Some(socket), None) => {
294             // run_until() returns an Result<Result<..>> which the ? operator lets us flatten.
295             ex.run_until(handler.run(socket, &ex))?
296         }
297         (None, Some(device_name)) => {
298             let device = VvuPciDevice::new(device_name.as_str(), BlockBackend::MAX_QUEUE_NUM)?;
299             ex.run_until(handler.run_vvu(device, &ex))?
300         }
301         _ => unreachable!("Must be checked above"),
302     }
303 }
304