devices/virtio/block/
asynchronous.rs

1// Copyright 2021 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
5use std::cell::RefCell;
6use std::collections::BTreeMap;
7use std::collections::BTreeSet;
8use std::io;
9use std::io::Write;
10use std::mem::size_of;
11#[cfg(windows)]
12use std::num::NonZeroU32;
13use std::rc::Rc;
14use std::result;
15use std::sync::atomic::AtomicU64;
16use std::sync::atomic::Ordering;
17use std::sync::Arc;
18use std::time::Duration;
19
20use anyhow::Context;
21use base::debug;
22use base::error;
23use base::info;
24use base::warn;
25use base::AsRawDescriptor;
26use base::Error as SysError;
27use base::Event;
28use base::RawDescriptor;
29use base::Result as SysResult;
30use base::Timer;
31use base::Tube;
32use base::TubeError;
33use base::WorkerThread;
34use cros_async::sync::RwLock as AsyncRwLock;
35use cros_async::AsyncError;
36use cros_async::AsyncTube;
37use cros_async::EventAsync;
38use cros_async::Executor;
39use cros_async::ExecutorKind;
40use cros_async::IoOptions;
41use cros_async::TimerAsync;
42use data_model::Le16;
43use data_model::Le32;
44use data_model::Le64;
45use disk::AsyncDisk;
46use disk::DiskFile;
47use futures::channel::mpsc;
48use futures::channel::oneshot;
49use futures::pin_mut;
50use futures::stream::FuturesUnordered;
51use futures::stream::StreamExt;
52use futures::FutureExt;
53use remain::sorted;
54use snapshot::AnySnapshot;
55use thiserror::Error as ThisError;
56use virtio_sys::virtio_config::VIRTIO_F_RING_PACKED;
57use vm_control::DiskControlCommand;
58use vm_control::DiskControlResult;
59use vm_memory::GuestMemory;
60use zerocopy::IntoBytes;
61
62use crate::virtio::async_utils;
63use crate::virtio::block::sys::*;
64use crate::virtio::block::DiskOption;
65use crate::virtio::copy_config;
66use crate::virtio::device_constants::block::virtio_blk_config;
67use crate::virtio::device_constants::block::virtio_blk_discard_write_zeroes;
68use crate::virtio::device_constants::block::virtio_blk_req_header;
69use crate::virtio::device_constants::block::VIRTIO_BLK_DISCARD_WRITE_ZEROES_FLAG_UNMAP;
70use crate::virtio::device_constants::block::VIRTIO_BLK_F_BLK_SIZE;
71use crate::virtio::device_constants::block::VIRTIO_BLK_F_DISCARD;
72use crate::virtio::device_constants::block::VIRTIO_BLK_F_FLUSH;
73use crate::virtio::device_constants::block::VIRTIO_BLK_F_MQ;
74use crate::virtio::device_constants::block::VIRTIO_BLK_F_RO;
75use crate::virtio::device_constants::block::VIRTIO_BLK_F_SEG_MAX;
76use crate::virtio::device_constants::block::VIRTIO_BLK_F_WRITE_ZEROES;
77use crate::virtio::device_constants::block::VIRTIO_BLK_S_IOERR;
78use crate::virtio::device_constants::block::VIRTIO_BLK_S_OK;
79use crate::virtio::device_constants::block::VIRTIO_BLK_S_UNSUPP;
80use crate::virtio::device_constants::block::VIRTIO_BLK_T_DISCARD;
81use crate::virtio::device_constants::block::VIRTIO_BLK_T_FLUSH;
82use crate::virtio::device_constants::block::VIRTIO_BLK_T_GET_ID;
83use crate::virtio::device_constants::block::VIRTIO_BLK_T_IN;
84use crate::virtio::device_constants::block::VIRTIO_BLK_T_OUT;
85use crate::virtio::device_constants::block::VIRTIO_BLK_T_WRITE_ZEROES;
86use crate::virtio::DescriptorChain;
87use crate::virtio::DeviceType;
88use crate::virtio::Interrupt;
89use crate::virtio::Queue;
90use crate::virtio::Reader;
91use crate::virtio::VirtioDevice;
92use crate::virtio::Writer;
93use crate::PciAddress;
94
95const DEFAULT_QUEUE_SIZE: u16 = 256;
96const DEFAULT_NUM_QUEUES: u16 = 16;
97
98const SECTOR_SHIFT: u8 = 9;
99const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
100
101const MAX_DISCARD_SECTORS: u32 = u32::MAX;
102const MAX_WRITE_ZEROES_SECTORS: u32 = u32::MAX;
103// Arbitrary limits for number of discard/write zeroes segments.
104const MAX_DISCARD_SEG: u32 = 32;
105const MAX_WRITE_ZEROES_SEG: u32 = 32;
106// Hard-coded to 64 KiB (in 512-byte sectors) for now,
107// but this should probably be based on cluster size for qcow.
108const DISCARD_SECTOR_ALIGNMENT: u32 = 128;
109
110#[sorted]
111#[derive(ThisError, Debug)]
112enum ExecuteError {
113    #[error("failed to copy ID string: {0}")]
114    CopyId(io::Error),
115    #[error("failed to perform discard or write zeroes; sector={sector} num_sectors={num_sectors} flags={flags}; {ioerr:?}")]
116    DiscardWriteZeroes {
117        ioerr: Option<disk::Error>,
118        sector: u64,
119        num_sectors: u32,
120        flags: u32,
121    },
122    #[error("failed to flush: {0}")]
123    Flush(disk::Error),
124    #[error("not enough space in descriptor chain to write status")]
125    MissingStatus,
126    #[error("out of range")]
127    OutOfRange,
128    #[error("failed to read message: {0}")]
129    Read(io::Error),
130    #[error("io error reading {length} bytes from sector {sector}: {desc_error}")]
131    ReadIo {
132        length: usize,
133        sector: u64,
134        desc_error: disk::Error,
135    },
136    #[error("read only; request_type={request_type}")]
137    ReadOnly { request_type: u32 },
138    #[error("failed to recieve command message: {0}")]
139    ReceivingCommand(TubeError),
140    #[error("failed to send command response: {0}")]
141    SendingResponse(TubeError),
142    #[error("couldn't reset the timer: {0}")]
143    TimerReset(base::Error),
144    #[error("too many segments: {0} > {0}")]
145    TooManySegments(usize, usize),
146    #[error("unsupported ({0})")]
147    Unsupported(u32),
148    #[error("io error writing {length} bytes from sector {sector}: {desc_error}")]
149    WriteIo {
150        length: usize,
151        sector: u64,
152        desc_error: disk::Error,
153    },
154    #[error("failed to write request status: {0}")]
155    WriteStatus(io::Error),
156}
157
158enum LogLevel {
159    Debug,
160    Error,
161}
162
163impl ExecuteError {
164    fn status(&self) -> u8 {
165        match self {
166            ExecuteError::CopyId(_) => VIRTIO_BLK_S_IOERR,
167            ExecuteError::DiscardWriteZeroes { .. } => VIRTIO_BLK_S_IOERR,
168            ExecuteError::Flush(_) => VIRTIO_BLK_S_IOERR,
169            ExecuteError::MissingStatus => VIRTIO_BLK_S_IOERR,
170            ExecuteError::OutOfRange => VIRTIO_BLK_S_IOERR,
171            ExecuteError::Read(_) => VIRTIO_BLK_S_IOERR,
172            ExecuteError::ReadIo { .. } => VIRTIO_BLK_S_IOERR,
173            ExecuteError::ReadOnly { .. } => VIRTIO_BLK_S_IOERR,
174            ExecuteError::ReceivingCommand(_) => VIRTIO_BLK_S_IOERR,
175            ExecuteError::SendingResponse(_) => VIRTIO_BLK_S_IOERR,
176            ExecuteError::TimerReset(_) => VIRTIO_BLK_S_IOERR,
177            ExecuteError::TooManySegments(_, _) => VIRTIO_BLK_S_IOERR,
178            ExecuteError::WriteIo { .. } => VIRTIO_BLK_S_IOERR,
179            ExecuteError::WriteStatus(_) => VIRTIO_BLK_S_IOERR,
180            ExecuteError::Unsupported(_) => VIRTIO_BLK_S_UNSUPP,
181        }
182    }
183
184    fn log_level(&self) -> LogLevel {
185        match self {
186            // Log disk I/O errors at debug level to avoid flooding the logs.
187            ExecuteError::ReadIo { .. }
188            | ExecuteError::WriteIo { .. }
189            | ExecuteError::Flush { .. }
190            | ExecuteError::DiscardWriteZeroes { .. } => LogLevel::Debug,
191            // Log all other failures as errors.
192            _ => LogLevel::Error,
193        }
194    }
195}
196
197/// Errors that happen in block outside of executing a request.
198/// This includes errors during resize and flush operations.
199#[sorted]
200#[derive(ThisError, Debug)]
201enum ControlError {
202    #[error("failed to fdatasync the disk: {0}")]
203    FdatasyncDisk(disk::Error),
204    #[error("couldn't get a value from a timer for flushing: {0}")]
205    FlushTimer(AsyncError),
206}
207
208/// Maximum length of the virtio-block ID string field.
209const ID_LEN: usize = 20;
210
211/// Virtio block device identifier.
212/// This is an ASCII string terminated by a \0, unless all 20 bytes are used,
213/// in which case the \0 terminator is omitted.
214type BlockId = [u8; ID_LEN];
215
216/// Tracks the state of an anynchronous disk.
217struct DiskState {
218    disk_image: Box<dyn AsyncDisk>,
219    read_only: bool,
220    sparse: bool,
221    id: BlockId,
222    dontcache: bool,
223    /// A DiskState is owned by each worker's executor and cannot be shared by workers, thus
224    /// `worker_shared_state` holds the state shared by workers in Arc.
225    worker_shared_state: Arc<AsyncRwLock<WorkerSharedState>>,
226}
227
228/// Disk state which can be modified by other worker threads
229struct WorkerSharedState {
230    disk_size: Arc<AtomicU64>,
231}
232
233async fn process_one_request(
234    avail_desc: &mut DescriptorChain,
235    disk_state: &AsyncRwLock<DiskState>,
236    flush_timer: &RefCell<TimerAsync<Timer>>,
237    flush_timer_armed: &RefCell<bool>,
238) -> result::Result<usize, ExecuteError> {
239    let reader = &mut avail_desc.reader;
240    let writer = &mut avail_desc.writer;
241
242    // The last byte of the buffer is virtio_blk_req::status.
243    // Split it into a separate Writer so that status_writer is the final byte and
244    // the original writer is left with just the actual block I/O data.
245    let available_bytes = writer.available_bytes();
246    let status_offset = available_bytes
247        .checked_sub(1)
248        .ok_or(ExecuteError::MissingStatus)?;
249    let mut status_writer = writer.split_at(status_offset);
250
251    let status = match BlockAsync::execute_request(
252        reader,
253        writer,
254        disk_state,
255        flush_timer,
256        flush_timer_armed,
257    )
258    .await
259    {
260        Ok(()) => VIRTIO_BLK_S_OK,
261        Err(e) => {
262            match e.log_level() {
263                LogLevel::Debug => debug!("failed executing disk request: {:#}", e),
264                LogLevel::Error => error!("failed executing disk request: {:#}", e),
265            }
266            e.status()
267        }
268    };
269
270    status_writer
271        .write_all(&[status])
272        .map_err(ExecuteError::WriteStatus)?;
273    Ok(available_bytes)
274}
275
276/// Process one descriptor chain asynchronously.
277async fn process_one_chain(
278    queue: &RefCell<Queue>,
279    mut avail_desc: DescriptorChain,
280    disk_state: &AsyncRwLock<DiskState>,
281    flush_timer: &RefCell<TimerAsync<Timer>>,
282    flush_timer_armed: &RefCell<bool>,
283) {
284    let len = match process_one_request(&mut avail_desc, disk_state, flush_timer, flush_timer_armed)
285        .await
286    {
287        Ok(len) => len,
288        Err(e) => {
289            error!("block: failed to handle request: {:#}", e);
290            0
291        }
292    };
293
294    let mut queue = queue.borrow_mut();
295    queue.add_used_with_bytes_written(avail_desc, len as u32);
296    queue.trigger_interrupt();
297}
298
299// There is one async task running `handle_queue` per virtio queue in use.
300// Receives messages from the guest and queues a task to complete the operations with the async
301// executor.
302async fn handle_queue(
303    disk_state: Rc<AsyncRwLock<DiskState>>,
304    queue: Queue,
305    evt: EventAsync,
306    flush_timer: Rc<RefCell<TimerAsync<Timer>>>,
307    flush_timer_armed: Rc<RefCell<bool>>,
308    mut stop_rx: oneshot::Receiver<()>,
309) -> Queue {
310    let queue = RefCell::new(queue);
311    let mut background_tasks = FuturesUnordered::new();
312    let evt_future = evt.next_val().fuse();
313    pin_mut!(evt_future);
314    loop {
315        // Wait for the next signal from `evt` and process `background_tasks` in the meantime.
316        //
317        // NOTE: We can't call `evt.next_val()` directly in the `select!` expression. That would
318        // create a new future each time, which, in the completion-based async backends like
319        // io_uring, means we'd submit a new syscall each time (i.e. a race condition on the
320        // eventfd).
321        futures::select! {
322            _ = background_tasks.next() => continue,
323            res = evt_future => {
324                evt_future.set(evt.next_val().fuse());
325                if let Err(e) = res {
326                    error!("Failed to read the next queue event: {:#}", e);
327                    continue;
328                }
329            }
330            _ = stop_rx => {
331                // Process all the descriptors we've already popped from the queue so that we leave
332                // the queue in a consistent state.
333                background_tasks.collect::<()>().await;
334                return queue.into_inner();
335            }
336        };
337        while let Some(descriptor_chain) = queue.borrow_mut().pop() {
338            background_tasks.push(process_one_chain(
339                &queue,
340                descriptor_chain,
341                &disk_state,
342                &flush_timer,
343                &flush_timer_armed,
344            ));
345        }
346    }
347}
348
349async fn handle_command_tube(
350    command_tube: &Option<AsyncTube>,
351    interrupt: &RefCell<Option<Interrupt>>,
352    disk_state: Rc<AsyncRwLock<DiskState>>,
353) -> Result<(), ExecuteError> {
354    let command_tube = match command_tube {
355        Some(c) => c,
356        None => {
357            futures::future::pending::<()>().await;
358            return Ok(());
359        }
360    };
361    loop {
362        match command_tube.next().await {
363            Ok(command) => {
364                let resp = match command {
365                    DiskControlCommand::Resize { new_size } => resize(&disk_state, new_size).await,
366                };
367
368                let resp_clone = resp.clone();
369                command_tube
370                    .send(resp_clone)
371                    .await
372                    .map_err(ExecuteError::SendingResponse)?;
373                if let DiskControlResult::Ok = resp {
374                    if let Some(interrupt) = &*interrupt.borrow() {
375                        interrupt.signal_config_changed();
376                    }
377                }
378            }
379            Err(e) => return Err(ExecuteError::ReceivingCommand(e)),
380        }
381    }
382}
383
384async fn resize(disk_state: &AsyncRwLock<DiskState>, new_size: u64) -> DiskControlResult {
385    // Acquire exclusive, mutable access to the state so the virtqueue task won't be able to read
386    // the state while resizing.
387    let disk_state = disk_state.lock().await;
388    // Prevent any other worker threads won't be able to do IO.
389    let worker_shared_state = Arc::clone(&disk_state.worker_shared_state);
390    let worker_shared_state = worker_shared_state.lock().await;
391
392    if disk_state.read_only {
393        error!("Attempted to resize read-only block device");
394        return DiskControlResult::Err(SysError::new(libc::EROFS));
395    }
396
397    info!("Resizing block device to {} bytes", new_size);
398
399    if let Err(e) = disk_state.disk_image.set_len(new_size) {
400        error!("Resizing disk failed! {:#}", e);
401        return DiskControlResult::Err(SysError::new(libc::EIO));
402    }
403
404    // Allocate new space if the disk image is not sparse.
405    if !disk_state.sparse {
406        if let Err(e) = disk_state.disk_image.allocate(0, new_size) {
407            error!("Allocating disk space after resize failed! {:#}", e);
408            return DiskControlResult::Err(SysError::new(libc::EIO));
409        }
410    }
411
412    if let Ok(new_disk_size) = disk_state.disk_image.get_len() {
413        worker_shared_state
414            .disk_size
415            .store(new_disk_size, Ordering::Release);
416    }
417    DiskControlResult::Ok
418}
419
420/// Periodically flushes the disk when the given timer fires.
421async fn flush_disk(
422    disk_state: Rc<AsyncRwLock<DiskState>>,
423    timer: TimerAsync<Timer>,
424    armed: Rc<RefCell<bool>>,
425) -> Result<(), ControlError> {
426    loop {
427        timer.wait().await.map_err(ControlError::FlushTimer)?;
428        if !*armed.borrow() {
429            continue;
430        }
431
432        // Reset armed before calling fdatasync to guarantee that IO requests that started after we
433        // call fdatasync will be committed eventually.
434        *armed.borrow_mut() = false;
435
436        disk_state
437            .read_lock()
438            .await
439            .disk_image
440            .fdatasync()
441            .await
442            .map_err(ControlError::FdatasyncDisk)?;
443    }
444}
445
446enum WorkerCmd {
447    StartQueue {
448        index: usize,
449        queue: Queue,
450    },
451    StopQueue {
452        index: usize,
453        // Once the queue is stopped, it will be sent back over `response_tx`.
454        // `None` indicates that there was no queue at the given index.
455        response_tx: oneshot::Sender<Option<Queue>>,
456    },
457    // Stop all queues without recovering the queues' state and without completing any queued up
458    // work .
459    AbortQueues {
460        // Once the queues are stopped, a `()` value will be sent back over `response_tx`.
461        response_tx: oneshot::Sender<()>,
462    },
463}
464
465// The main worker thread. Initialized the asynchronous worker tasks and passes them to the executor
466// to be processed.
467//
468// `disk_state` is wrapped by `AsyncRwLock`, which provides both shared and exclusive locks. It's
469// because the state can be read from the virtqueue task while the control task is processing a
470// resizing command.
471async fn run_worker(
472    ex: &Executor,
473    disk_state: &Rc<AsyncRwLock<DiskState>>,
474    control_tube: &Option<AsyncTube>,
475    mut worker_rx: mpsc::UnboundedReceiver<WorkerCmd>,
476    kill_evt: Event,
477) -> anyhow::Result<()> {
478    // One flush timer per disk.
479    let timer = Timer::new().expect("Failed to create a timer");
480    let flush_timer_armed = Rc::new(RefCell::new(false));
481
482    // Handles control requests.
483    let control_interrupt = RefCell::new(None);
484    let control = handle_command_tube(control_tube, &control_interrupt, disk_state.clone()).fuse();
485    pin_mut!(control);
486
487    // Handle all the queues in one sub-select call.
488    let flush_timer = Rc::new(RefCell::new(
489        TimerAsync::new(
490            // Call try_clone() to share the same underlying FD with the `flush_disk` task.
491            timer.try_clone().expect("Failed to clone flush_timer"),
492            ex,
493        )
494        .expect("Failed to create an async timer"),
495    ));
496
497    // Flushes the disk periodically.
498    let flush_timer2 = TimerAsync::new(timer, ex).expect("Failed to create an async timer");
499    let disk_flush = flush_disk(disk_state.clone(), flush_timer2, flush_timer_armed.clone()).fuse();
500    pin_mut!(disk_flush);
501
502    // Exit if the kill event is triggered.
503    let kill = async_utils::await_and_exit(ex, kill_evt).fuse();
504    pin_mut!(kill);
505
506    // Running queue handlers.
507    let mut queue_handlers = FuturesUnordered::new();
508    // Async stop functions for queue handlers, by queue index.
509    let mut queue_handler_stop_fns = std::collections::BTreeMap::new();
510
511    loop {
512        futures::select! {
513            _ = queue_handlers.next() => continue,
514            r = disk_flush => return r.context("failed to flush a disk"),
515            r = control => return r.context("failed to handle a control request"),
516            r = kill => return r.context("failed to wait on the kill event"),
517            worker_cmd = worker_rx.next() => {
518                match worker_cmd {
519                    None => anyhow::bail!("worker control channel unexpectedly closed"),
520                    Some(WorkerCmd::StartQueue{index, queue}) => {
521                        if control_interrupt.borrow().is_none() {
522                            *control_interrupt.borrow_mut() = Some(queue.interrupt().clone());
523                        }
524
525                        let (tx, rx) = oneshot::channel();
526                        let kick_evt = queue.event().try_clone().expect("Failed to clone queue event");
527                        let (handle_queue_future, remote_handle) = handle_queue(
528                            Rc::clone(disk_state),
529                            queue,
530                            EventAsync::new(kick_evt, ex).expect("Failed to create async event for queue"),
531                            Rc::clone(&flush_timer),
532                            Rc::clone(&flush_timer_armed),
533                            rx,
534                        ).remote_handle();
535                        let old_stop_fn = queue_handler_stop_fns.insert(index, move || {
536                            // Ask the handler to stop.
537                            tx.send(()).unwrap_or_else(|_| panic!("queue handler channel closed early"));
538                            // Wait for its return value.
539                            remote_handle
540                        });
541
542                        // If there was already a handler for this index, stop it before adding the
543                        // new handler future.
544                        if let Some(stop_fn) = old_stop_fn {
545                            warn!("Starting new queue handler without stopping old handler");
546                            // Unfortunately we can't just do `stop_fn().await` because the actual
547                            // work we are waiting on is in `queue_handlers`. So, run both.
548                            let mut fut = stop_fn().fuse();
549                            loop {
550                                futures::select! {
551                                    _ = queue_handlers.next() => continue,
552                                    _queue = fut => break,
553                                }
554                            }
555                        }
556
557                        queue_handlers.push(handle_queue_future);
558                    }
559                    Some(WorkerCmd::StopQueue{index, response_tx}) => {
560                        match queue_handler_stop_fns.remove(&index) {
561                            Some(stop_fn) => {
562                                // NOTE: This await is blocking the select loop. If we want to
563                                // support stopping queues concurrently, then it needs to be moved.
564                                // For now, keep it simple.
565                                //
566                                // Unfortunately we can't just do `stop_fn().await` because the
567                                // actual work we are waiting on is in `queue_handlers`. So, run
568                                // both.
569                                let mut fut = stop_fn().fuse();
570                                let queue = loop {
571                                    futures::select! {
572                                        _ = queue_handlers.next() => continue,
573                                        queue = fut => break queue,
574                                    }
575                                };
576
577                                // If this is the last queue, drop references to the interrupt so
578                                // that, when queues are started up again, we'll use the new
579                                // interrupt passed with the first queue.
580                                if queue_handlers.is_empty() {
581                                    *control_interrupt.borrow_mut() = None;
582                                }
583
584                                let _ = response_tx.send(Some(queue));
585                            }
586                            None => { let _ = response_tx.send(None); },
587                        }
588
589                    }
590                    Some(WorkerCmd::AbortQueues{response_tx}) => {
591                        queue_handlers.clear();
592                        queue_handler_stop_fns.clear();
593
594                        *control_interrupt.borrow_mut() = None;
595
596                        let _ = response_tx.send(());
597                    }
598                }
599            }
600        };
601    }
602}
603
604/// Virtio device for exposing block level read/write operations on a host file.
605pub struct BlockAsync {
606    // We need to make boot_index public bc the field is used by the main crate to determine boot
607    // order
608    boot_index: Option<usize>,
609    // `None` iff `self.worker_per_queue == false` and the worker thread is running.
610    disk_image: Option<Box<dyn DiskFile>>,
611    disk_size: Arc<AtomicU64>,
612    avail_features: u64,
613    read_only: bool,
614    sparse: bool,
615    seg_max: u32,
616    block_size: u32,
617    id: BlockId,
618    control_tube: Option<Tube>,
619    queue_sizes: Vec<u16>,
620    pub(super) executor_kind: ExecutorKind,
621    // If `worker_per_queue == true`, `worker_threads` contains the worker for each running queue
622    // by index. Otherwise, contains the monolithic worker for all queues at index 0.
623    //
624    // Once a thread is started, we never stop it, except when `BlockAsync` itself is dropped. That
625    // is because we cannot easily convert the `AsyncDisk` back to a `DiskFile` when backed by
626    // Overlapped I/O on Windows because the file becomes permanently associated with the IOCP
627    // instance of the async executor.
628    worker_threads: BTreeMap<usize, (WorkerThread<()>, mpsc::UnboundedSender<WorkerCmd>)>,
629    shared_state: Arc<AsyncRwLock<WorkerSharedState>>,
630    // Whether to run worker threads in parallel for each queue
631    worker_per_queue: bool,
632    // Indices of running queues.
633    // TODO: The worker already tracks this. Only need it here to stop queues on sleep. Maybe add a
634    // worker cmd to stop all at once, then we can delete this field.
635    activated_queues: BTreeSet<usize>,
636    #[cfg(windows)]
637    pub(super) io_concurrency: u32,
638    pci_address: Option<PciAddress>,
639    dontcache: bool,
640}
641
642impl BlockAsync {
643    /// Create a new virtio block device that operates on the given AsyncDisk.
644    pub fn new(
645        base_features: u64,
646        disk_image: Box<dyn DiskFile>,
647        disk_option: &DiskOption,
648        control_tube: Option<Tube>,
649        queue_size: Option<u16>,
650        num_queues: Option<u16>,
651    ) -> SysResult<BlockAsync> {
652        let read_only = disk_option.read_only;
653        let sparse = disk_option.sparse;
654        let block_size = disk_option.block_size;
655        let packed_queue = disk_option.packed_queue;
656        let id = disk_option.id.unwrap_or_default();
657        let mut worker_per_queue = disk_option.multiple_workers;
658        // Automatically disable multiple workers if the disk image can't be cloned.
659        if worker_per_queue && disk_image.try_clone().is_err() {
660            base::warn!("multiple workers requested, but not supported by disk image type");
661            worker_per_queue = false;
662        }
663        let executor_kind = disk_option.async_executor.unwrap_or_default();
664        let boot_index = disk_option.bootindex;
665        #[cfg(windows)]
666        let io_concurrency = disk_option.io_concurrency.get();
667
668        if block_size % SECTOR_SIZE as u32 != 0 {
669            error!(
670                "Block size {} is not a multiple of {}.",
671                block_size, SECTOR_SIZE,
672            );
673            return Err(SysError::new(libc::EINVAL));
674        }
675        let disk_size = disk_image.get_len()?;
676        if disk_size % block_size as u64 != 0 {
677            warn!(
678                "Disk size {} is not a multiple of block size {}; \
679                 the remainder will not be visible to the guest.",
680                disk_size, block_size,
681            );
682        }
683        let num_queues = num_queues.unwrap_or(DEFAULT_NUM_QUEUES);
684        let multi_queue = match num_queues {
685            0 => panic!("Number of queues cannot be zero for a block device"),
686            1 => false,
687            _ => true,
688        };
689        let q_size = queue_size.unwrap_or(DEFAULT_QUEUE_SIZE);
690        if !q_size.is_power_of_two() {
691            error!("queue size {} is not a power of 2.", q_size);
692            return Err(SysError::new(libc::EINVAL));
693        }
694        let queue_sizes = vec![q_size; num_queues as usize];
695
696        let avail_features =
697            Self::build_avail_features(base_features, read_only, sparse, multi_queue, packed_queue);
698
699        let seg_max = get_seg_max(q_size);
700
701        let disk_size = Arc::new(AtomicU64::new(disk_size));
702        let shared_state = Arc::new(AsyncRwLock::new(WorkerSharedState {
703            disk_size: disk_size.clone(),
704        }));
705
706        Ok(BlockAsync {
707            disk_image: Some(disk_image),
708            disk_size,
709            avail_features,
710            read_only,
711            sparse,
712            seg_max,
713            block_size,
714            id,
715            queue_sizes,
716            worker_threads: BTreeMap::new(),
717            shared_state,
718            worker_per_queue,
719            control_tube,
720            executor_kind,
721            activated_queues: BTreeSet::new(),
722            boot_index,
723            #[cfg(windows)]
724            io_concurrency,
725            pci_address: disk_option.pci_address,
726            dontcache: disk_option.dontcache,
727        })
728    }
729
730    /// Returns the feature flags given the specified attributes.
731    fn build_avail_features(
732        base_features: u64,
733        read_only: bool,
734        sparse: bool,
735        multi_queue: bool,
736        packed_queue: bool,
737    ) -> u64 {
738        let mut avail_features = base_features;
739        if read_only {
740            avail_features |= 1 << VIRTIO_BLK_F_RO;
741        } else {
742            if sparse {
743                avail_features |= 1 << VIRTIO_BLK_F_DISCARD;
744            }
745            avail_features |= 1 << VIRTIO_BLK_F_FLUSH;
746            avail_features |= 1 << VIRTIO_BLK_F_WRITE_ZEROES;
747        }
748        avail_features |= 1 << VIRTIO_BLK_F_SEG_MAX;
749        avail_features |= 1 << VIRTIO_BLK_F_BLK_SIZE;
750        if multi_queue {
751            avail_features |= 1 << VIRTIO_BLK_F_MQ;
752        }
753        if packed_queue {
754            avail_features |= 1 << VIRTIO_F_RING_PACKED;
755        }
756        avail_features
757    }
758
759    // Execute a single block device request.
760    // `writer` includes the data region only; the status byte is not included.
761    // It is up to the caller to convert the result of this function into a status byte
762    // and write it to the expected location in guest memory.
763    async fn execute_request(
764        reader: &mut Reader,
765        writer: &mut Writer,
766        disk_state: &AsyncRwLock<DiskState>,
767        flush_timer: &RefCell<TimerAsync<Timer>>,
768        flush_timer_armed: &RefCell<bool>,
769    ) -> result::Result<(), ExecuteError> {
770        // Acquire immutable access to prevent tasks from resizing disk.
771        let disk_state = disk_state.read_lock().await;
772        // Acquire immutable access to prevent other worker threads from resizing disk.
773        let worker_shared_state = disk_state.worker_shared_state.read_lock().await;
774
775        let req_header: virtio_blk_req_header = reader.read_obj().map_err(ExecuteError::Read)?;
776
777        let req_type = req_header.req_type.to_native();
778        let sector = req_header.sector.to_native();
779
780        if disk_state.read_only && req_type != VIRTIO_BLK_T_IN && req_type != VIRTIO_BLK_T_GET_ID {
781            return Err(ExecuteError::ReadOnly {
782                request_type: req_type,
783            });
784        }
785
786        /// Check that a request accesses only data within the disk's current size.
787        /// All parameters are in units of bytes.
788        fn check_range(
789            io_start: u64,
790            io_length: u64,
791            disk_size: u64,
792        ) -> result::Result<(), ExecuteError> {
793            let io_end = io_start
794                .checked_add(io_length)
795                .ok_or(ExecuteError::OutOfRange)?;
796            if io_end > disk_size {
797                Err(ExecuteError::OutOfRange)
798            } else {
799                Ok(())
800            }
801        }
802
803        let disk_size = worker_shared_state.disk_size.load(Ordering::Relaxed);
804        match req_type {
805            VIRTIO_BLK_T_IN => {
806                let data_len = writer.available_bytes();
807                if data_len == 0 {
808                    return Ok(());
809                }
810                let offset = sector
811                    .checked_shl(u32::from(SECTOR_SHIFT))
812                    .ok_or(ExecuteError::OutOfRange)?;
813                check_range(offset, data_len as u64, disk_size)?;
814                let disk_image = &disk_state.disk_image;
815                writer
816                    .write_all_from_at_fut(
817                        &**disk_image,
818                        data_len,
819                        offset,
820                        IoOptions {
821                            dontcache: disk_state.dontcache,
822                        },
823                    )
824                    .await
825                    .map_err(|desc_error| ExecuteError::ReadIo {
826                        length: data_len,
827                        sector,
828                        desc_error,
829                    })?;
830            }
831            VIRTIO_BLK_T_OUT => {
832                let data_len = reader.available_bytes();
833                if data_len == 0 {
834                    return Ok(());
835                }
836                let offset = sector
837                    .checked_shl(u32::from(SECTOR_SHIFT))
838                    .ok_or(ExecuteError::OutOfRange)?;
839                check_range(offset, data_len as u64, disk_size)?;
840                let disk_image = &disk_state.disk_image;
841                reader
842                    .read_exact_to_at_fut(
843                        &**disk_image,
844                        data_len,
845                        offset,
846                        IoOptions {
847                            dontcache: disk_state.dontcache,
848                        },
849                    )
850                    .await
851                    .map_err(|desc_error| ExecuteError::WriteIo {
852                        length: data_len,
853                        sector,
854                        desc_error,
855                    })?;
856
857                if !*flush_timer_armed.borrow() {
858                    *flush_timer_armed.borrow_mut() = true;
859
860                    let flush_delay = Duration::from_secs(60);
861                    flush_timer
862                        .borrow_mut()
863                        .reset_oneshot(flush_delay)
864                        .map_err(ExecuteError::TimerReset)?;
865                }
866            }
867            VIRTIO_BLK_T_DISCARD | VIRTIO_BLK_T_WRITE_ZEROES => {
868                if req_type == VIRTIO_BLK_T_DISCARD && !disk_state.sparse {
869                    // Discard is a hint; if this is a non-sparse disk, just ignore it.
870                    return Ok(());
871                }
872
873                let seg_count =
874                    reader.available_bytes() / size_of::<virtio_blk_discard_write_zeroes>();
875                let seg_max = if req_type == VIRTIO_BLK_T_DISCARD {
876                    MAX_DISCARD_SEG as usize
877                } else {
878                    MAX_WRITE_ZEROES_SEG as usize
879                };
880                if seg_count > seg_max {
881                    return Err(ExecuteError::TooManySegments(seg_count, seg_max));
882                }
883
884                while reader.available_bytes() >= size_of::<virtio_blk_discard_write_zeroes>() {
885                    let seg: virtio_blk_discard_write_zeroes =
886                        reader.read_obj().map_err(ExecuteError::Read)?;
887
888                    let sector = seg.sector.to_native();
889                    let num_sectors = seg.num_sectors.to_native();
890                    let flags = seg.flags.to_native();
891
892                    let valid_flags = if req_type == VIRTIO_BLK_T_WRITE_ZEROES {
893                        VIRTIO_BLK_DISCARD_WRITE_ZEROES_FLAG_UNMAP
894                    } else {
895                        0
896                    };
897
898                    if (flags & !valid_flags) != 0 {
899                        return Err(ExecuteError::DiscardWriteZeroes {
900                            ioerr: None,
901                            sector,
902                            num_sectors,
903                            flags,
904                        });
905                    }
906
907                    let offset = sector
908                        .checked_shl(u32::from(SECTOR_SHIFT))
909                        .ok_or(ExecuteError::OutOfRange)?;
910                    let length = u64::from(num_sectors)
911                        .checked_shl(u32::from(SECTOR_SHIFT))
912                        .ok_or(ExecuteError::OutOfRange)?;
913                    check_range(offset, length, disk_size)?;
914
915                    if req_type == VIRTIO_BLK_T_DISCARD {
916                        // Since Discard is just a hint and some filesystems may not implement
917                        // FALLOC_FL_PUNCH_HOLE, ignore punch_hole errors.
918                        let _ = disk_state.disk_image.punch_hole(offset, length).await;
919                    } else {
920                        disk_state
921                            .disk_image
922                            .write_zeroes_at(offset, length)
923                            .await
924                            .map_err(|e| ExecuteError::DiscardWriteZeroes {
925                                ioerr: Some(e),
926                                sector,
927                                num_sectors,
928                                flags,
929                            })?;
930                    }
931                }
932            }
933            VIRTIO_BLK_T_FLUSH => {
934                disk_state
935                    .disk_image
936                    .fdatasync()
937                    .await
938                    .map_err(ExecuteError::Flush)?;
939
940                if *flush_timer_armed.borrow() {
941                    flush_timer
942                        .borrow_mut()
943                        .clear()
944                        .map_err(ExecuteError::TimerReset)?;
945                    *flush_timer_armed.borrow_mut() = false;
946                }
947            }
948            VIRTIO_BLK_T_GET_ID => {
949                writer
950                    .write_all(&disk_state.id)
951                    .map_err(ExecuteError::CopyId)?;
952            }
953            t => return Err(ExecuteError::Unsupported(t)),
954        };
955        Ok(())
956    }
957
958    /// Builds and returns the config structure used to specify block features.
959    fn build_config_space(
960        disk_size: u64,
961        seg_max: u32,
962        block_size: u32,
963        num_queues: u16,
964    ) -> virtio_blk_config {
965        virtio_blk_config {
966            // If the image is not a multiple of the sector size, the tail bits are not exposed.
967            capacity: Le64::from(disk_size >> SECTOR_SHIFT),
968            seg_max: Le32::from(seg_max),
969            blk_size: Le32::from(block_size),
970            num_queues: Le16::from(num_queues),
971            max_discard_sectors: Le32::from(MAX_DISCARD_SECTORS),
972            discard_sector_alignment: Le32::from(DISCARD_SECTOR_ALIGNMENT),
973            max_write_zeroes_sectors: Le32::from(MAX_WRITE_ZEROES_SECTORS),
974            write_zeroes_may_unmap: 1,
975            max_discard_seg: Le32::from(MAX_DISCARD_SEG),
976            max_write_zeroes_seg: Le32::from(MAX_WRITE_ZEROES_SEG),
977            ..Default::default()
978        }
979    }
980
981    /// Get the worker for a queue, starting it if necessary.
982    // NOTE: Can't use `BTreeMap::entry` because it requires an exclusive ref for the whole branch.
983    #[allow(clippy::map_entry)]
984    fn start_worker(
985        &mut self,
986        idx: usize,
987    ) -> anyhow::Result<&(WorkerThread<()>, mpsc::UnboundedSender<WorkerCmd>)> {
988        let key = if self.worker_per_queue { idx } else { 0 };
989        if self.worker_threads.contains_key(&key) {
990            return Ok(self.worker_threads.get(&key).unwrap());
991        }
992
993        let ex = self.create_executor();
994        let control_tube = self.control_tube.take();
995        let dontcache = self.dontcache;
996        let disk_image = if self.worker_per_queue {
997            self.disk_image
998                .as_ref()
999                .context("Failed to ref a disk image")?
1000                .try_clone()
1001                .context("Failed to clone a disk image")?
1002        } else {
1003            self.disk_image
1004                .take()
1005                .context("Failed to take a disk image")?
1006        };
1007        let read_only = self.read_only;
1008        let sparse = self.sparse;
1009        let id = self.id;
1010        let worker_shared_state = self.shared_state.clone();
1011
1012        let (worker_tx, worker_rx) = mpsc::unbounded();
1013        let worker_thread = WorkerThread::start("virtio_blk", move |kill_evt| {
1014            let async_control =
1015                control_tube.map(|c| AsyncTube::new(&ex, c).expect("failed to create async tube"));
1016
1017            let async_image = match disk_image.to_async_disk(&ex) {
1018                Ok(d) => d,
1019                Err(e) => panic!("Failed to create async disk {e:#}"),
1020            };
1021
1022            let disk_state = Rc::new(AsyncRwLock::new(DiskState {
1023                disk_image: async_image,
1024                read_only,
1025                sparse,
1026                id,
1027                dontcache,
1028                worker_shared_state,
1029            }));
1030
1031            if let Err(err_string) = ex
1032                .run_until(async {
1033                    let r = run_worker(&ex, &disk_state, &async_control, worker_rx, kill_evt).await;
1034                    // Flush any in-memory disk image state to file.
1035                    if let Err(e) = disk_state.lock().await.disk_image.flush().await {
1036                        error!("failed to flush disk image when stopping worker: {e:?}");
1037                    }
1038                    r
1039                })
1040                .expect("run_until failed")
1041            {
1042                error!("{:#}", err_string);
1043            }
1044        });
1045        match self.worker_threads.entry(key) {
1046            std::collections::btree_map::Entry::Occupied(_) => unreachable!(),
1047            std::collections::btree_map::Entry::Vacant(e) => {
1048                Ok(e.insert((worker_thread, worker_tx)))
1049            }
1050        }
1051    }
1052
1053    pub fn start_queue(
1054        &mut self,
1055        idx: usize,
1056        queue: Queue,
1057        _mem: GuestMemory,
1058    ) -> anyhow::Result<()> {
1059        let (_, worker_tx) = self.start_worker(idx)?;
1060        worker_tx
1061            .unbounded_send(WorkerCmd::StartQueue { index: idx, queue })
1062            .expect("worker channel closed early");
1063        self.activated_queues.insert(idx);
1064        Ok(())
1065    }
1066
1067    pub fn stop_queue(&mut self, idx: usize) -> anyhow::Result<Queue> {
1068        // TODO: Consider stopping the worker thread if this is the last queue managed by it. Then,
1069        // simplify `virtio_sleep` and/or `reset` methods.
1070        let (_, worker_tx) = self
1071            .worker_threads
1072            .get(if self.worker_per_queue { &idx } else { &0 })
1073            .context("worker not found")?;
1074        let (response_tx, response_rx) = oneshot::channel();
1075        worker_tx
1076            .unbounded_send(WorkerCmd::StopQueue {
1077                index: idx,
1078                response_tx,
1079            })
1080            .expect("worker channel closed early");
1081        let queue = cros_async::block_on(async {
1082            response_rx
1083                .await
1084                .expect("response_rx closed early")
1085                .context("queue not found")
1086        })?;
1087        self.activated_queues.remove(&idx);
1088        Ok(queue)
1089    }
1090}
1091
1092impl VirtioDevice for BlockAsync {
1093    fn keep_rds(&self) -> Vec<RawDescriptor> {
1094        let mut keep_rds = Vec::new();
1095
1096        if let Some(disk_image) = &self.disk_image {
1097            keep_rds.extend(disk_image.as_raw_descriptors());
1098        }
1099
1100        if let Some(control_tube) = &self.control_tube {
1101            keep_rds.push(control_tube.as_raw_descriptor());
1102        }
1103
1104        keep_rds
1105    }
1106
1107    fn features(&self) -> u64 {
1108        self.avail_features
1109    }
1110
1111    fn device_type(&self) -> DeviceType {
1112        DeviceType::Block
1113    }
1114
1115    fn queue_max_sizes(&self) -> &[u16] {
1116        &self.queue_sizes
1117    }
1118
1119    fn read_config(&self, offset: u64, data: &mut [u8]) {
1120        let config_space = {
1121            let disk_size = self.disk_size.load(Ordering::Acquire);
1122            Self::build_config_space(
1123                disk_size,
1124                self.seg_max,
1125                self.block_size,
1126                self.queue_sizes.len() as u16,
1127            )
1128        };
1129        copy_config(data, 0, config_space.as_bytes(), offset);
1130    }
1131
1132    fn activate(
1133        &mut self,
1134        mem: GuestMemory,
1135        _interrupt: Interrupt,
1136        queues: BTreeMap<usize, Queue>,
1137    ) -> anyhow::Result<()> {
1138        for (i, q) in queues {
1139            self.start_queue(i, q, mem.clone())?;
1140        }
1141        Ok(())
1142    }
1143
1144    fn reset(&mut self) -> anyhow::Result<()> {
1145        for (_, (_, worker_tx)) in self.worker_threads.iter_mut() {
1146            let (response_tx, response_rx) = oneshot::channel();
1147            worker_tx
1148                .unbounded_send(WorkerCmd::AbortQueues { response_tx })
1149                .expect("worker channel closed early");
1150            cros_async::block_on(async { response_rx.await.expect("response_rx closed early") });
1151        }
1152        self.activated_queues.clear();
1153        Ok(())
1154    }
1155
1156    fn virtio_sleep(&mut self) -> anyhow::Result<Option<BTreeMap<usize, Queue>>> {
1157        // Reclaim the queues from workers.
1158        let mut queues = BTreeMap::new();
1159        for index in self.activated_queues.clone() {
1160            queues.insert(index, self.stop_queue(index)?);
1161        }
1162        if queues.is_empty() {
1163            return Ok(None); // Not activated.
1164        }
1165        Ok(Some(queues))
1166    }
1167
1168    fn virtio_wake(
1169        &mut self,
1170        queues_state: Option<(GuestMemory, Interrupt, BTreeMap<usize, Queue>)>,
1171    ) -> anyhow::Result<()> {
1172        if let Some((mem, _interrupt, queues)) = queues_state {
1173            for (i, q) in queues {
1174                self.start_queue(i, q, mem.clone())?
1175            }
1176        }
1177        Ok(())
1178    }
1179
1180    fn virtio_snapshot(&mut self) -> anyhow::Result<AnySnapshot> {
1181        // `virtio_sleep` ensures there is no pending state, except for the `Queue`s, which are
1182        // handled at a higher layer.
1183        AnySnapshot::to_any(())
1184    }
1185
1186    fn virtio_restore(&mut self, data: AnySnapshot) -> anyhow::Result<()> {
1187        let () = AnySnapshot::from_any(data)?;
1188        Ok(())
1189    }
1190
1191    fn pci_address(&self) -> Option<PciAddress> {
1192        self.pci_address
1193    }
1194
1195    fn bootorder_fw_cfg(&self, pci_slot: u8) -> Option<(Vec<u8>, usize)> {
1196        self.boot_index
1197            .map(|s| (format!("scsi@{pci_slot}/disk@0,0").as_bytes().to_vec(), s))
1198    }
1199}
1200
1201#[cfg(test)]
1202mod tests {
1203    use std::fs::File;
1204    use std::mem::size_of_val;
1205    use std::sync::atomic::AtomicU64;
1206
1207    use data_model::Le32;
1208    use data_model::Le64;
1209    use disk::SingleFileDisk;
1210    use hypervisor::ProtectionType;
1211    use tempfile::tempfile;
1212    use tempfile::TempDir;
1213    use vm_memory::GuestAddress;
1214
1215    use super::*;
1216    use crate::suspendable_virtio_tests;
1217    use crate::virtio::base_features;
1218    use crate::virtio::descriptor_utils::create_descriptor_chain;
1219    use crate::virtio::descriptor_utils::DescriptorType;
1220    use crate::virtio::QueueConfig;
1221
1222    #[test]
1223    fn read_size() {
1224        let f = tempfile().unwrap();
1225        f.set_len(0x1000).unwrap();
1226
1227        let features = base_features(ProtectionType::Unprotected);
1228        let disk_option = DiskOption::default();
1229        let b = BlockAsync::new(features, Box::new(f), &disk_option, None, None, None).unwrap();
1230        let mut num_sectors = [0u8; 4];
1231        b.read_config(0, &mut num_sectors);
1232        // size is 0x1000, so num_sectors is 8 (4096/512).
1233        assert_eq!([0x08, 0x00, 0x00, 0x00], num_sectors);
1234        let mut msw_sectors = [0u8; 4];
1235        b.read_config(4, &mut msw_sectors);
1236        // size is 0x1000, so msw_sectors is 0.
1237        assert_eq!([0x00, 0x00, 0x00, 0x00], msw_sectors);
1238    }
1239
1240    #[test]
1241    fn read_block_size() {
1242        let f = tempfile().unwrap();
1243        f.set_len(0x1000).unwrap();
1244
1245        let features = base_features(ProtectionType::Unprotected);
1246        let disk_option = DiskOption {
1247            block_size: 4096,
1248            sparse: false,
1249            ..Default::default()
1250        };
1251        let b = BlockAsync::new(features, Box::new(f), &disk_option, None, None, None).unwrap();
1252        let mut blk_size = [0u8; 4];
1253        b.read_config(20, &mut blk_size);
1254        // blk_size should be 4096 (0x1000).
1255        assert_eq!([0x00, 0x10, 0x00, 0x00], blk_size);
1256    }
1257
1258    #[test]
1259    fn read_features() {
1260        let tempdir = TempDir::new().unwrap();
1261        let mut path = tempdir.path().to_owned();
1262        path.push("disk_image");
1263
1264        // Feature bits 0-23 and 50-127 are specific for the device type, but
1265        // at the moment crosvm only supports 64 bits of feature bits.
1266        const DEVICE_FEATURE_BITS: u64 = 0xffffff;
1267
1268        // read-write block device
1269        {
1270            let f = File::create(&path).unwrap();
1271            let features = base_features(ProtectionType::Unprotected);
1272            let disk_option = DiskOption::default();
1273            let b = BlockAsync::new(features, Box::new(f), &disk_option, None, None, None).unwrap();
1274            // writable device should set VIRTIO_BLK_F_FLUSH + VIRTIO_BLK_F_DISCARD
1275            // + VIRTIO_BLK_F_WRITE_ZEROES + VIRTIO_BLK_F_BLK_SIZE + VIRTIO_BLK_F_SEG_MAX
1276            // + VIRTIO_BLK_F_MQ
1277            assert_eq!(0x7244, b.features() & DEVICE_FEATURE_BITS);
1278        }
1279
1280        // read-write block device, non-sparse
1281        {
1282            let f = File::create(&path).unwrap();
1283            let features = base_features(ProtectionType::Unprotected);
1284            let disk_option = DiskOption {
1285                sparse: false,
1286                ..Default::default()
1287            };
1288            let b = BlockAsync::new(features, Box::new(f), &disk_option, None, None, None).unwrap();
1289            // writable device should set VIRTIO_F_FLUSH + VIRTIO_BLK_F_RO
1290            // + VIRTIO_BLK_F_BLK_SIZE + VIRTIO_BLK_F_SEG_MAX + VIRTIO_BLK_F_MQ
1291            assert_eq!(0x5244, b.features() & DEVICE_FEATURE_BITS);
1292        }
1293
1294        // read-only block device
1295        {
1296            let f = File::create(&path).unwrap();
1297            let features = base_features(ProtectionType::Unprotected);
1298            let disk_option = DiskOption {
1299                read_only: true,
1300                ..Default::default()
1301            };
1302            let b = BlockAsync::new(features, Box::new(f), &disk_option, None, None, None).unwrap();
1303            // read-only device should set VIRTIO_BLK_F_RO
1304            // + VIRTIO_BLK_F_BLK_SIZE + VIRTIO_BLK_F_SEG_MAX + VIRTIO_BLK_F_MQ
1305            assert_eq!(0x1064, b.features() & DEVICE_FEATURE_BITS);
1306        }
1307    }
1308
1309    #[test]
1310    fn check_pci_adress_configurability() {
1311        let f = tempfile().unwrap();
1312
1313        let features = base_features(ProtectionType::Unprotected);
1314        let disk_option = DiskOption {
1315            pci_address: Some(PciAddress {
1316                bus: 0,
1317                dev: 1,
1318                func: 1,
1319            }),
1320            ..Default::default()
1321        };
1322        let b = BlockAsync::new(features, Box::new(f), &disk_option, None, None, None).unwrap();
1323
1324        assert_eq!(b.pci_address(), disk_option.pci_address);
1325    }
1326
1327    #[test]
1328    fn check_runtime_blk_queue_configurability() {
1329        let tempdir = TempDir::new().unwrap();
1330        let mut path = tempdir.path().to_owned();
1331        path.push("disk_image");
1332        let features = base_features(ProtectionType::Unprotected);
1333
1334        // Default case
1335        let f = File::create(&path).unwrap();
1336        let disk_option = DiskOption::default();
1337        let b = BlockAsync::new(features, Box::new(f), &disk_option, None, None, None).unwrap();
1338        assert_eq!(
1339            [DEFAULT_QUEUE_SIZE; DEFAULT_NUM_QUEUES as usize],
1340            b.queue_max_sizes()
1341        );
1342
1343        // Single queue of size 128
1344        let f = File::create(&path).unwrap();
1345        let disk_option = DiskOption::default();
1346        let b = BlockAsync::new(
1347            features,
1348            Box::new(f),
1349            &disk_option,
1350            None,
1351            Some(128),
1352            Some(1),
1353        )
1354        .unwrap();
1355        assert_eq!([128; 1], b.queue_max_sizes());
1356        // Single queue device should not set VIRTIO_BLK_F_MQ
1357        assert_eq!(0, b.features() & (1 << VIRTIO_BLK_F_MQ) as u64);
1358    }
1359
1360    #[test]
1361    fn read_last_sector() {
1362        let ex = Executor::new().expect("creating an executor failed");
1363
1364        let f = tempfile().unwrap();
1365        let disk_size = 0x1000;
1366        f.set_len(disk_size).unwrap();
1367        let af = SingleFileDisk::new(f, &ex).expect("Failed to create SFD");
1368
1369        let mem = Rc::new(
1370            GuestMemory::new(&[(GuestAddress(0u64), 4 * 1024 * 1024)])
1371                .expect("Creating guest memory failed."),
1372        );
1373
1374        let req_hdr = virtio_blk_req_header {
1375            req_type: Le32::from(VIRTIO_BLK_T_IN),
1376            reserved: Le32::from(0),
1377            sector: Le64::from(7), // Disk is 8 sectors long, so this is the last valid sector.
1378        };
1379        mem.write_obj_at_addr(req_hdr, GuestAddress(0x1000))
1380            .expect("writing req failed");
1381
1382        let mut avail_desc = create_descriptor_chain(
1383            &mem,
1384            GuestAddress(0x100),  // Place descriptor chain at 0x100.
1385            GuestAddress(0x1000), // Describe buffer at 0x1000.
1386            vec![
1387                // Request header
1388                (DescriptorType::Readable, size_of_val(&req_hdr) as u32),
1389                // I/O buffer (1 sector of data)
1390                (DescriptorType::Writable, 512),
1391                // Request status
1392                (DescriptorType::Writable, 1),
1393            ],
1394            0,
1395        )
1396        .expect("create_descriptor_chain failed");
1397
1398        let timer = Timer::new().expect("Failed to create a timer");
1399        let flush_timer = Rc::new(RefCell::new(
1400            TimerAsync::new(timer, &ex).expect("Failed to create an async timer"),
1401        ));
1402        let flush_timer_armed = Rc::new(RefCell::new(false));
1403
1404        let disk_state = Rc::new(AsyncRwLock::new(DiskState {
1405            disk_image: Box::new(af),
1406            read_only: false,
1407            sparse: true,
1408            id: Default::default(),
1409            dontcache: false,
1410            worker_shared_state: Arc::new(AsyncRwLock::new(WorkerSharedState {
1411                disk_size: Arc::new(AtomicU64::new(disk_size)),
1412            })),
1413        }));
1414
1415        let fut = process_one_request(
1416            &mut avail_desc,
1417            &disk_state,
1418            &flush_timer,
1419            &flush_timer_armed,
1420        );
1421
1422        ex.run_until(fut)
1423            .expect("running executor failed")
1424            .expect("execute failed");
1425
1426        let status_offset = GuestAddress((0x1000 + size_of_val(&req_hdr) + 512) as u64);
1427        let status = mem.read_obj_from_addr::<u8>(status_offset).unwrap();
1428        assert_eq!(status, VIRTIO_BLK_S_OK);
1429    }
1430
1431    #[test]
1432    fn read_beyond_last_sector() {
1433        let f = tempfile().unwrap();
1434        let disk_size = 0x1000;
1435        f.set_len(disk_size).unwrap();
1436        let mem = Rc::new(
1437            GuestMemory::new(&[(GuestAddress(0u64), 4 * 1024 * 1024)])
1438                .expect("Creating guest memory failed."),
1439        );
1440
1441        let req_hdr = virtio_blk_req_header {
1442            req_type: Le32::from(VIRTIO_BLK_T_IN),
1443            reserved: Le32::from(0),
1444            sector: Le64::from(7), // Disk is 8 sectors long, so this is the last valid sector.
1445        };
1446        mem.write_obj_at_addr(req_hdr, GuestAddress(0x1000))
1447            .expect("writing req failed");
1448
1449        let mut avail_desc = create_descriptor_chain(
1450            &mem,
1451            GuestAddress(0x100),  // Place descriptor chain at 0x100.
1452            GuestAddress(0x1000), // Describe buffer at 0x1000.
1453            vec![
1454                // Request header
1455                (DescriptorType::Readable, size_of_val(&req_hdr) as u32),
1456                // I/O buffer (2 sectors of data - overlap the end of the disk).
1457                (DescriptorType::Writable, 512 * 2),
1458                // Request status
1459                (DescriptorType::Writable, 1),
1460            ],
1461            0,
1462        )
1463        .expect("create_descriptor_chain failed");
1464
1465        let ex = Executor::new().expect("creating an executor failed");
1466
1467        let af = SingleFileDisk::new(f, &ex).expect("Failed to create SFD");
1468        let timer = Timer::new().expect("Failed to create a timer");
1469        let flush_timer = Rc::new(RefCell::new(
1470            TimerAsync::new(timer, &ex).expect("Failed to create an async timer"),
1471        ));
1472        let flush_timer_armed = Rc::new(RefCell::new(false));
1473        let disk_state = Rc::new(AsyncRwLock::new(DiskState {
1474            disk_image: Box::new(af),
1475            read_only: false,
1476            sparse: true,
1477            id: Default::default(),
1478            dontcache: false,
1479            worker_shared_state: Arc::new(AsyncRwLock::new(WorkerSharedState {
1480                disk_size: Arc::new(AtomicU64::new(disk_size)),
1481            })),
1482        }));
1483
1484        let fut = process_one_request(
1485            &mut avail_desc,
1486            &disk_state,
1487            &flush_timer,
1488            &flush_timer_armed,
1489        );
1490
1491        ex.run_until(fut)
1492            .expect("running executor failed")
1493            .expect("execute failed");
1494
1495        let status_offset = GuestAddress((0x1000 + size_of_val(&req_hdr) + 512 * 2) as u64);
1496        let status = mem.read_obj_from_addr::<u8>(status_offset).unwrap();
1497        assert_eq!(status, VIRTIO_BLK_S_IOERR);
1498    }
1499
1500    #[test]
1501    fn get_id() {
1502        let ex = Executor::new().expect("creating an executor failed");
1503
1504        let f = tempfile().unwrap();
1505        let disk_size = 0x1000;
1506        f.set_len(disk_size).unwrap();
1507
1508        let mem = GuestMemory::new(&[(GuestAddress(0u64), 4 * 1024 * 1024)])
1509            .expect("Creating guest memory failed.");
1510
1511        let req_hdr = virtio_blk_req_header {
1512            req_type: Le32::from(VIRTIO_BLK_T_GET_ID),
1513            reserved: Le32::from(0),
1514            sector: Le64::from(0),
1515        };
1516        mem.write_obj_at_addr(req_hdr, GuestAddress(0x1000))
1517            .expect("writing req failed");
1518
1519        let mut avail_desc = create_descriptor_chain(
1520            &mem,
1521            GuestAddress(0x100),  // Place descriptor chain at 0x100.
1522            GuestAddress(0x1000), // Describe buffer at 0x1000.
1523            vec![
1524                // Request header
1525                (DescriptorType::Readable, size_of_val(&req_hdr) as u32),
1526                // I/O buffer (20 bytes for serial)
1527                (DescriptorType::Writable, 20),
1528                // Request status
1529                (DescriptorType::Writable, 1),
1530            ],
1531            0,
1532        )
1533        .expect("create_descriptor_chain failed");
1534
1535        let af = SingleFileDisk::new(f, &ex).expect("Failed to create SFD");
1536        let timer = Timer::new().expect("Failed to create a timer");
1537        let flush_timer = Rc::new(RefCell::new(
1538            TimerAsync::new(timer, &ex).expect("Failed to create an async timer"),
1539        ));
1540        let flush_timer_armed = Rc::new(RefCell::new(false));
1541
1542        let id = b"a20-byteserialnumber";
1543
1544        let disk_state = Rc::new(AsyncRwLock::new(DiskState {
1545            disk_image: Box::new(af),
1546            read_only: false,
1547            sparse: true,
1548            id: *id,
1549            dontcache: false,
1550            worker_shared_state: Arc::new(AsyncRwLock::new(WorkerSharedState {
1551                disk_size: Arc::new(AtomicU64::new(disk_size)),
1552            })),
1553        }));
1554
1555        let fut = process_one_request(
1556            &mut avail_desc,
1557            &disk_state,
1558            &flush_timer,
1559            &flush_timer_armed,
1560        );
1561
1562        ex.run_until(fut)
1563            .expect("running executor failed")
1564            .expect("execute failed");
1565
1566        let status_offset = GuestAddress((0x1000 + size_of_val(&req_hdr) + 512) as u64);
1567        let status = mem.read_obj_from_addr::<u8>(status_offset).unwrap();
1568        assert_eq!(status, VIRTIO_BLK_S_OK);
1569
1570        let id_offset = GuestAddress(0x1000 + size_of_val(&req_hdr) as u64);
1571        let returned_id = mem.read_obj_from_addr::<[u8; 20]>(id_offset).unwrap();
1572        assert_eq!(returned_id, *id);
1573    }
1574
1575    #[test]
1576    fn reset_and_reactivate_single_worker() {
1577        reset_and_reactivate(false, None);
1578    }
1579
1580    #[test]
1581    fn reset_and_reactivate_multiple_workers() {
1582        reset_and_reactivate(true, None);
1583    }
1584
1585    #[test]
1586    #[cfg(windows)]
1587    fn reset_and_reactivate_overrlapped_io() {
1588        reset_and_reactivate(
1589            false,
1590            Some(
1591                cros_async::sys::windows::ExecutorKindSys::Overlapped { concurrency: None }.into(),
1592            ),
1593        );
1594    }
1595
1596    fn reset_and_reactivate(
1597        enables_multiple_workers: bool,
1598        async_executor: Option<cros_async::ExecutorKind>,
1599    ) {
1600        // Create an empty disk image
1601        let f = tempfile::NamedTempFile::new().unwrap();
1602        f.as_file().set_len(0x1000).unwrap();
1603        // Close the file so that it is possible for the disk implementation to take exclusive
1604        // access when opening it.
1605        let path: tempfile::TempPath = f.into_temp_path();
1606
1607        // Create an empty guest memory
1608        let mem = GuestMemory::new(&[(GuestAddress(0u64), 4 * 1024 * 1024)])
1609            .expect("Creating guest memory failed.");
1610
1611        // Create a control tube.
1612        // NOTE: We don't want to drop the vmm half of the tube. That would cause the worker thread
1613        // will immediately fail, which isn't what we want to test in this case.
1614        let (_control_tube, control_tube_device) = Tube::pair().unwrap();
1615
1616        // Create a BlockAsync to test
1617        let features = base_features(ProtectionType::Unprotected);
1618        let id = b"Block serial number\0";
1619        let disk_option = DiskOption {
1620            path: path.to_path_buf(),
1621            read_only: true,
1622            id: Some(*id),
1623            sparse: false,
1624            multiple_workers: enables_multiple_workers,
1625            async_executor,
1626            ..Default::default()
1627        };
1628        let disk_image = disk_option.open().unwrap();
1629        let mut b = BlockAsync::new(
1630            features,
1631            disk_image,
1632            &disk_option,
1633            Some(control_tube_device),
1634            None,
1635            None,
1636        )
1637        .unwrap();
1638
1639        let interrupt = Interrupt::new_for_test();
1640
1641        // activate with queues of an arbitrary size.
1642        let mut q0 = QueueConfig::new(DEFAULT_QUEUE_SIZE, 0);
1643        q0.set_ready(true);
1644        let q0 = q0
1645            .activate(&mem, Event::new().unwrap(), interrupt.clone())
1646            .expect("QueueConfig::activate");
1647
1648        let mut q1 = QueueConfig::new(DEFAULT_QUEUE_SIZE, 0);
1649        q1.set_ready(true);
1650        let q1 = q1
1651            .activate(&mem, Event::new().unwrap(), interrupt.clone())
1652            .expect("QueueConfig::activate");
1653
1654        b.activate(mem.clone(), interrupt, BTreeMap::from([(0, q0), (1, q1)]))
1655            .expect("activate should succeed");
1656        // assert resources are consumed
1657        if !enables_multiple_workers {
1658            assert!(
1659                b.disk_image.is_none(),
1660                "BlockAsync should not have a disk image"
1661            );
1662        }
1663        assert!(
1664            b.control_tube.is_none(),
1665            "BlockAsync should not have a control tube"
1666        );
1667        assert_eq!(
1668            b.worker_threads.len(),
1669            if enables_multiple_workers { 2 } else { 1 }
1670        );
1671
1672        // reset and assert resources are still not back (should be in the worker thread)
1673        assert!(b.reset().is_ok(), "reset should succeed");
1674        if !enables_multiple_workers {
1675            assert!(
1676                b.disk_image.is_none(),
1677                "BlockAsync should not have a disk image"
1678            );
1679        }
1680        assert!(
1681            b.control_tube.is_none(),
1682            "BlockAsync should not have a control tube"
1683        );
1684        assert_eq!(
1685            b.worker_threads.len(),
1686            if enables_multiple_workers { 2 } else { 1 }
1687        );
1688        assert_eq!(b.id, *b"Block serial number\0");
1689
1690        // re-activate should succeed
1691        let interrupt = Interrupt::new_for_test();
1692        let mut q0 = QueueConfig::new(DEFAULT_QUEUE_SIZE, 0);
1693        q0.set_ready(true);
1694        let q0 = q0
1695            .activate(&mem, Event::new().unwrap(), interrupt.clone())
1696            .expect("QueueConfig::activate");
1697
1698        let mut q1 = QueueConfig::new(DEFAULT_QUEUE_SIZE, 0);
1699        q1.set_ready(true);
1700        let q1 = q1
1701            .activate(&mem, Event::new().unwrap(), interrupt.clone())
1702            .expect("QueueConfig::activate");
1703
1704        b.activate(mem, interrupt, BTreeMap::from([(0, q0), (1, q1)]))
1705            .expect("re-activate should succeed");
1706    }
1707
1708    #[test]
1709    fn resize_with_single_worker() {
1710        resize(false);
1711    }
1712
1713    #[test]
1714    fn resize_with_multiple_workers() {
1715        // Test resize handled by one worker affect the whole state
1716        resize(true);
1717    }
1718
1719    fn resize(enables_multiple_workers: bool) {
1720        // disk image size constants
1721        let original_size = 0x1000;
1722        let resized_size = 0x2000;
1723
1724        // Create an empty disk image
1725        let f = tempfile().unwrap();
1726        f.set_len(original_size).unwrap();
1727        let disk_image: Box<dyn DiskFile> = Box::new(f);
1728        assert_eq!(disk_image.get_len().unwrap(), original_size);
1729
1730        // Create an empty guest memory
1731        let mem = GuestMemory::new(&[(GuestAddress(0u64), 4 * 1024 * 1024)])
1732            .expect("Creating guest memory failed.");
1733
1734        // Create a control tube
1735        let (control_tube, control_tube_device) = Tube::pair().unwrap();
1736
1737        // Create a BlockAsync to test
1738        let features = base_features(ProtectionType::Unprotected);
1739        let disk_option = DiskOption {
1740            multiple_workers: enables_multiple_workers,
1741            ..Default::default()
1742        };
1743        let mut b = BlockAsync::new(
1744            features,
1745            disk_image.try_clone().unwrap(),
1746            &disk_option,
1747            Some(control_tube_device),
1748            None,
1749            None,
1750        )
1751        .unwrap();
1752
1753        let interrupt = Interrupt::new_for_test();
1754
1755        // activate with queues of an arbitrary size.
1756        let mut q0 = QueueConfig::new(DEFAULT_QUEUE_SIZE, 0);
1757        q0.set_ready(true);
1758        let q0 = q0
1759            .activate(&mem, Event::new().unwrap(), interrupt.clone())
1760            .expect("QueueConfig::activate");
1761
1762        let mut q1 = QueueConfig::new(DEFAULT_QUEUE_SIZE, 0);
1763        q1.set_ready(true);
1764        let q1 = q1
1765            .activate(&mem, Event::new().unwrap(), interrupt.clone())
1766            .expect("QueueConfig::activate");
1767
1768        b.activate(mem, interrupt.clone(), BTreeMap::from([(0, q0), (1, q1)]))
1769            .expect("activate should succeed");
1770
1771        // assert the original size first
1772        assert_eq!(
1773            b.disk_size.load(Ordering::Acquire),
1774            original_size,
1775            "disk_size should be the original size first"
1776        );
1777        let mut capacity = [0u8; 8];
1778        b.read_config(0, &mut capacity);
1779        assert_eq!(
1780            capacity,
1781            // original_size (0x1000) >> SECTOR_SHIFT (9) = 0x8
1782            [0x8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
1783            "read_config should read the original capacity first"
1784        );
1785
1786        // assert resize works
1787        control_tube
1788            .send(&DiskControlCommand::Resize {
1789                new_size: resized_size,
1790            })
1791            .unwrap();
1792        assert_eq!(
1793            control_tube.recv::<DiskControlResult>().unwrap(),
1794            DiskControlResult::Ok,
1795            "resize command should succeed"
1796        );
1797        assert_eq!(
1798            b.disk_size.load(Ordering::Acquire),
1799            resized_size,
1800            "disk_size should be resized to the new size"
1801        );
1802        assert_eq!(
1803            disk_image.get_len().unwrap(),
1804            resized_size,
1805            "underlying disk image should be resized to the new size"
1806        );
1807        let mut capacity = [0u8; 8];
1808        b.read_config(0, &mut capacity);
1809        assert_eq!(
1810            capacity,
1811            // resized_size (0x2000) >> SECTOR_SHIFT (9) = 0x10
1812            [0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
1813            "read_config should read the resized capacity"
1814        );
1815        // Wait until the blk signals the interrupt
1816        interrupt
1817            .get_interrupt_evt()
1818            .wait()
1819            .expect("interrupt should be signaled");
1820
1821        assert_eq!(
1822            interrupt.read_interrupt_status(),
1823            crate::virtio::INTERRUPT_STATUS_CONFIG_CHANGED as u8,
1824            "INTERRUPT_STATUS_CONFIG_CHANGED should be signaled"
1825        );
1826    }
1827
1828    #[test]
1829    fn run_worker_threads() {
1830        // Create an empty duplicable disk image
1831        let f = tempfile().unwrap();
1832        f.set_len(0x1000).unwrap();
1833        let disk_image: Box<dyn DiskFile> = Box::new(f);
1834
1835        // Create an empty guest memory
1836        let mem = GuestMemory::new(&[(GuestAddress(0u64), 4 * 1024 * 1024)])
1837            .expect("Creating guest memory failed.");
1838
1839        // Create a BlockAsync to test with single worker thread
1840        let features = base_features(ProtectionType::Unprotected);
1841        let disk_option = DiskOption::default();
1842        let mut b = BlockAsync::new(
1843            features,
1844            disk_image.try_clone().unwrap(),
1845            &disk_option,
1846            None,
1847            None,
1848            None,
1849        )
1850        .unwrap();
1851
1852        // activate with queues of an arbitrary size.
1853        let interrupt = Interrupt::new_for_test();
1854        let mut q0 = QueueConfig::new(DEFAULT_QUEUE_SIZE, 0);
1855        q0.set_ready(true);
1856        let q0 = q0
1857            .activate(&mem, Event::new().unwrap(), interrupt.clone())
1858            .expect("QueueConfig::activate");
1859
1860        let mut q1 = QueueConfig::new(DEFAULT_QUEUE_SIZE, 0);
1861        q1.set_ready(true);
1862        let q1 = q1
1863            .activate(&mem, Event::new().unwrap(), interrupt.clone())
1864            .expect("QueueConfig::activate");
1865
1866        b.activate(mem.clone(), interrupt, BTreeMap::from([(0, q0), (1, q1)]))
1867            .expect("activate should succeed");
1868
1869        assert_eq!(b.worker_threads.len(), 1, "1 threads should be spawned.");
1870        drop(b);
1871
1872        // Create a BlockAsync to test with multiple worker threads
1873        let features = base_features(ProtectionType::Unprotected);
1874        let disk_option = DiskOption {
1875            read_only: true,
1876            sparse: false,
1877            multiple_workers: true,
1878            ..DiskOption::default()
1879        };
1880        let mut b = BlockAsync::new(features, disk_image, &disk_option, None, None, None).unwrap();
1881
1882        // activate should succeed
1883        let interrupt = Interrupt::new_for_test();
1884        let mut q0 = QueueConfig::new(DEFAULT_QUEUE_SIZE, 0);
1885        q0.set_ready(true);
1886        let q0 = q0
1887            .activate(&mem, Event::new().unwrap(), interrupt.clone())
1888            .expect("QueueConfig::activate");
1889
1890        let mut q1 = QueueConfig::new(DEFAULT_QUEUE_SIZE, 0);
1891        q1.set_ready(true);
1892        let q1 = q1
1893            .activate(&mem, Event::new().unwrap(), interrupt.clone())
1894            .expect("QueueConfig::activate");
1895
1896        b.activate(mem, interrupt, BTreeMap::from([(0, q0), (1, q1)]))
1897            .expect("activate should succeed");
1898
1899        assert_eq!(b.worker_threads.len(), 2, "2 threads should be spawned.");
1900    }
1901
1902    struct BlockContext {}
1903
1904    fn modify_device(_block_context: &mut BlockContext, b: &mut BlockAsync) {
1905        b.avail_features = !b.avail_features;
1906    }
1907
1908    fn create_device() -> (BlockContext, BlockAsync) {
1909        // Create an empty disk image
1910        let f = tempfile().unwrap();
1911        f.set_len(0x1000).unwrap();
1912        let disk_image: Box<dyn DiskFile> = Box::new(f);
1913
1914        // Create a BlockAsync to test
1915        let features = base_features(ProtectionType::Unprotected);
1916        let id = b"Block serial number\0";
1917        let disk_option = DiskOption {
1918            read_only: true,
1919            id: Some(*id),
1920            sparse: false,
1921            multiple_workers: true,
1922            ..Default::default()
1923        };
1924        (
1925            BlockContext {},
1926            BlockAsync::new(
1927                features,
1928                disk_image.try_clone().unwrap(),
1929                &disk_option,
1930                None,
1931                None,
1932                None,
1933            )
1934            .unwrap(),
1935        )
1936    }
1937
1938    #[cfg(any(target_os = "android", target_os = "linux"))]
1939    suspendable_virtio_tests!(asyncblock, create_device, 2, modify_device);
1940}