diff options
Diffstat (limited to 'devices/src/virtio')
-rw-r--r-- | devices/src/virtio/console.rs | 449 | ||||
-rw-r--r-- | devices/src/virtio/descriptor_utils.rs | 15 | ||||
-rw-r--r-- | devices/src/virtio/fs/filesystem.rs | 30 | ||||
-rw-r--r-- | devices/src/virtio/fs/fuse.rs | 91 | ||||
-rw-r--r-- | devices/src/virtio/fs/passthrough.rs | 67 | ||||
-rw-r--r-- | devices/src/virtio/fs/server.rs | 39 | ||||
-rw-r--r-- | devices/src/virtio/gpu/mod.rs | 131 | ||||
-rw-r--r-- | devices/src/virtio/gpu/protocol.rs | 120 | ||||
-rw-r--r-- | devices/src/virtio/gpu/virtio_3d_backend.rs | 269 | ||||
-rw-r--r-- | devices/src/virtio/gpu/virtio_gfxstream_backend.rs | 6 | ||||
-rw-r--r-- | devices/src/virtio/interrupt.rs | 2 | ||||
-rw-r--r-- | devices/src/virtio/mod.rs | 2 | ||||
-rw-r--r-- | devices/src/virtio/vhost/control_socket.rs | 36 | ||||
-rw-r--r-- | devices/src/virtio/vhost/mod.rs | 2 | ||||
-rw-r--r-- | devices/src/virtio/vhost/net.rs | 66 | ||||
-rw-r--r-- | devices/src/virtio/vhost/vsock.rs | 1 | ||||
-rw-r--r-- | devices/src/virtio/vhost/worker.rs | 135 | ||||
-rw-r--r-- | devices/src/virtio/virtio_device.rs | 4 | ||||
-rw-r--r-- | devices/src/virtio/virtio_pci_device.rs | 7 |
19 files changed, 1155 insertions, 317 deletions
diff --git a/devices/src/virtio/console.rs b/devices/src/virtio/console.rs new file mode 100644 index 0000000..38f5bf1 --- /dev/null +++ b/devices/src/virtio/console.rs @@ -0,0 +1,449 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use std::io::{self, Read}; +use std::os::unix::io::RawFd; +use std::sync::mpsc::{channel, Receiver, TryRecvError}; +use std::thread; + +use data_model::{DataInit, Le16, Le32}; +use sys_util::{error, EventFd, GuestMemory, PollContext, PollToken}; + +use super::{ + copy_config, Interrupt, Queue, Reader, VirtioDevice, Writer, TYPE_CONSOLE, VIRTIO_F_VERSION_1, +}; + +const QUEUE_SIZE: u16 = 256; + +// For now, just implement port 0 (receiveq and transmitq). +// If VIRTIO_CONSOLE_F_MULTIPORT is implemented, more queues will be needed. +const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE, QUEUE_SIZE]; + +#[derive(Copy, Clone, Debug, Default)] +#[repr(C)] +struct virtio_console_config { + cols: Le16, + rows: Le16, + max_nr_ports: Le32, + emerg_wr: Le32, +} + +// Safe because it only has data and has no implicit padding. +unsafe impl DataInit for virtio_console_config {} + +struct Worker { + mem: GuestMemory, + interrupt: Interrupt, + input: Option<Box<dyn io::Read + Send>>, + output: Option<Box<dyn io::Write + Send>>, +} + +fn write_output(output: &mut Box<dyn io::Write>, data: &[u8]) -> io::Result<()> { + output.write_all(&data)?; + output.flush() +} + +impl Worker { + fn process_transmit_request( + mut reader: Reader, + output: &mut Box<dyn io::Write>, + ) -> io::Result<u32> { + let len = reader.available_bytes(); + let mut data = vec![0u8; len]; + reader.read_exact(&mut data)?; + write_output(output, &data)?; + Ok(0) + } + + fn process_transmit_queue( + &mut self, + transmit_queue: &mut Queue, + output: &mut Box<dyn io::Write>, + ) { + let mut needs_interrupt = false; + while let Some(avail_desc) = transmit_queue.pop(&self.mem) { + let desc_index = avail_desc.index; + + let reader = match Reader::new(&self.mem, avail_desc) { + Ok(r) => r, + Err(e) => { + error!("console: failed to create reader: {}", e); + transmit_queue.add_used(&self.mem, desc_index, 0); + needs_interrupt = true; + continue; + } + }; + + let len = match Self::process_transmit_request(reader, output) { + Ok(written) => written, + Err(e) => { + error!("console: process_transmit_request failed: {}", e); + 0 + } + }; + + transmit_queue.add_used(&self.mem, desc_index, len); + needs_interrupt = true; + } + + if needs_interrupt { + self.interrupt.signal_used_queue(transmit_queue.vector); + } + } + + // Start a thread that reads self.input and sends the input back via the returned channel. + // + // `in_avail_evt` will be triggered by the thread when new input is available. + fn spawn_input_thread(&mut self, in_avail_evt: &EventFd) -> Option<Receiver<u8>> { + let mut rx = match self.input.take() { + Some(input) => input, + None => return None, + }; + + let (send_channel, recv_channel) = channel(); + + let thread_in_avail_evt = match in_avail_evt.try_clone() { + Ok(evt) => evt, + Err(e) => { + error!("failed to clone in_avail_evt: {}", e); + return None; + } + }; + + // The input thread runs in detached mode and will exit when channel is disconnected because + // the console device has been dropped. + let res = thread::Builder::new() + .name(format!("console_input")) + .spawn(move || { + let mut rx_buf = [0u8; 1]; + loop { + match rx.read(&mut rx_buf) { + Ok(0) => break, // Assume the stream of input has ended. + Ok(_) => { + if send_channel.send(rx_buf[0]).is_err() { + // The receiver has disconnected. + break; + } + thread_in_avail_evt.write(1).unwrap(); + } + Err(e) => { + // Being interrupted is not an error, but everything else is. + if e.kind() != io::ErrorKind::Interrupted { + error!( + "failed to read for bytes to queue into console device: {}", + e + ); + break; + } + } + } + } + }); + if let Err(e) = res { + error!("failed to spawn input thread: {}", e); + return None; + } + Some(recv_channel) + } + + // Check for input from `in_channel_opt` and transfer it to the receive queue, if any. + fn handle_input( + &mut self, + in_channel_opt: &mut Option<Receiver<u8>>, + receive_queue: &mut Queue, + ) { + let in_channel = match in_channel_opt.as_ref() { + Some(v) => v, + None => return, + }; + + while let Some(desc) = receive_queue.peek(&self.mem) { + let desc_index = desc.index; + let mut writer = match Writer::new(&self.mem, desc) { + Ok(w) => w, + Err(e) => { + error!("console: failed to create Writer: {}", e); + break; + } + }; + + let mut disconnected = false; + while writer.available_bytes() > 0 { + match in_channel.try_recv() { + Ok(byte) => { + writer.write_obj(byte).unwrap(); + } + Err(TryRecvError::Empty) => break, + Err(TryRecvError::Disconnected) => { + disconnected = true; + break; + } + } + } + + let bytes_written = writer.bytes_written() as u32; + + if bytes_written > 0 { + receive_queue.pop_peeked(&self.mem); + receive_queue.add_used(&self.mem, desc_index, bytes_written); + self.interrupt.signal_used_queue(receive_queue.vector); + } + + if disconnected { + // Set in_channel to None so that future handle_input calls exit early. + in_channel_opt.take(); + return; + } + + if bytes_written == 0 { + break; + } + } + } + + fn run(&mut self, mut queues: Vec<Queue>, mut queue_evts: Vec<EventFd>, kill_evt: EventFd) { + #[derive(PollToken)] + enum Token { + ReceiveQueueAvailable, + TransmitQueueAvailable, + InputAvailable, + InterruptResample, + Kill, + } + + // Device -> driver + let (mut receive_queue, receive_evt) = (queues.remove(0), queue_evts.remove(0)); + + // Driver -> device + let (mut transmit_queue, transmit_evt) = (queues.remove(0), queue_evts.remove(0)); + + let in_avail_evt = match EventFd::new() { + Ok(evt) => evt, + Err(e) => { + error!("failed creating EventFd: {}", e); + return; + } + }; + + // Spawn a separate thread to poll self.input. + // A thread is used because io::Read only provides a blocking interface, and there is no + // generic way to add an io::Read instance to a poll context (it may not be backed by a file + // descriptor). Moving the blocking read call to a separate thread and sending data back to + // the main worker thread with an event for notification bridges this gap. + let mut in_channel = self.spawn_input_thread(&in_avail_evt); + + let poll_ctx: PollContext<Token> = match PollContext::build_with(&[ + (&transmit_evt, Token::TransmitQueueAvailable), + (&receive_evt, Token::ReceiveQueueAvailable), + (&in_avail_evt, Token::InputAvailable), + (self.interrupt.get_resample_evt(), Token::InterruptResample), + (&kill_evt, Token::Kill), + ]) { + Ok(pc) => pc, + Err(e) => { + error!("failed creating PollContext: {}", e); + return; + } + }; + + let mut output: Box<dyn io::Write> = match self.output.take() { + Some(o) => o, + None => Box::new(io::sink()), + }; + + 'poll: loop { + let events = match poll_ctx.wait() { + Ok(v) => v, + Err(e) => { + error!("failed polling for events: {}", e); + break; + } + }; + + for event in events.iter_readable() { + match event.token() { + Token::TransmitQueueAvailable => { + if let Err(e) = transmit_evt.read() { + error!("failed reading transmit queue EventFd: {}", e); + break 'poll; + } + self.process_transmit_queue(&mut transmit_queue, &mut output); + } + Token::ReceiveQueueAvailable => { + if let Err(e) = receive_evt.read() { + error!("failed reading receive queue EventFd: {}", e); + break 'poll; + } + self.handle_input(&mut in_channel, &mut receive_queue); + } + Token::InputAvailable => { + if let Err(e) = in_avail_evt.read() { + error!("failed reading in_avail_evt: {}", e); + break 'poll; + } + self.handle_input(&mut in_channel, &mut receive_queue); + } + Token::InterruptResample => { + self.interrupt.interrupt_resample(); + } + Token::Kill => break 'poll, + } + } + } + } +} + +/// Virtio console device. +pub struct Console { + kill_evt: Option<EventFd>, + worker_thread: Option<thread::JoinHandle<Worker>>, + input: Option<Box<dyn io::Read + Send>>, + output: Option<Box<dyn io::Write + Send>>, + keep_fds: Vec<RawFd>, +} + +impl Console { + fn new( + input: Option<Box<dyn io::Read + Send>>, + output: Option<Box<dyn io::Write + Send>>, + keep_fds: Vec<RawFd>, + ) -> Console { + Console { + kill_evt: None, + worker_thread: None, + input, + output, + keep_fds, + } + } + + /// Constructs a console with input and output streams. + pub fn new_in_out( + input: Box<dyn io::Read + Send>, + out: Box<dyn io::Write + Send>, + keep_fds: Vec<RawFd>, + ) -> Console { + Self::new(Some(input), Some(out), keep_fds) + } + + /// Constructs a console with an output stream but no input. + pub fn new_out(out: Box<dyn io::Write + Send>, keep_fds: Vec<RawFd>) -> Console { + Self::new(None, Some(out), keep_fds) + } + + /// Constructs a console with no connected input or output. + pub fn new_sink() -> Console { + Self::new(None, None, Vec::new()) + } +} + +impl Drop for Console { + fn drop(&mut self) { + if let Some(kill_evt) = self.kill_evt.take() { + // Ignore the result because there is nothing we can do about it. + let _ = kill_evt.write(1); + } + + if let Some(worker_thread) = self.worker_thread.take() { + let _ = worker_thread.join(); + } + } +} + +impl VirtioDevice for Console { + fn keep_fds(&self) -> Vec<RawFd> { + self.keep_fds.clone() + } + + fn features(&self) -> u64 { + 1 << VIRTIO_F_VERSION_1 + } + + fn device_type(&self) -> u32 { + TYPE_CONSOLE + } + + fn queue_max_sizes(&self) -> &[u16] { + QUEUE_SIZES + } + + fn read_config(&self, offset: u64, data: &mut [u8]) { + let config = virtio_console_config { + max_nr_ports: 1.into(), + ..Default::default() + }; + copy_config(data, 0, config.as_slice(), offset); + } + + fn activate( + &mut self, + mem: GuestMemory, + interrupt: Interrupt, + queues: Vec<Queue>, + queue_evts: Vec<EventFd>, + ) { + if queues.len() < 2 || queue_evts.len() < 2 { + return; + } + + let (self_kill_evt, kill_evt) = match EventFd::new().and_then(|e| Ok((e.try_clone()?, e))) { + Ok(v) => v, + Err(e) => { + error!("failed creating kill EventFd pair: {}", e); + return; + } + }; + self.kill_evt = Some(self_kill_evt); + + let input = self.input.take(); + let output = self.output.take(); + + let worker_result = thread::Builder::new() + .name("virtio_console".to_string()) + .spawn(move || { + let mut worker = Worker { + mem, + interrupt, + input, + output, + }; + worker.run(queues, queue_evts, kill_evt); + worker + }); + + match worker_result { + Err(e) => { + error!("failed to spawn virtio_console worker: {}", e); + return; + } + Ok(join_handle) => { + self.worker_thread = Some(join_handle); + } + } + } + + fn reset(&mut self) -> bool { + if let Some(kill_evt) = self.kill_evt.take() { + if kill_evt.write(1).is_err() { + error!("{}: failed to notify the kill event", self.debug_label()); + return false; + } + } + + if let Some(worker_thread) = self.worker_thread.take() { + match worker_thread.join() { + Err(_) => { + error!("{}: failed to get back resources", self.debug_label()); + return false; + } + Ok(worker) => { + self.input = worker.input; + self.output = worker.output; + return true; + } + } + } + false + } +} diff --git a/devices/src/virtio/descriptor_utils.rs b/devices/src/virtio/descriptor_utils.rs index 723b0c6..990f147 100644 --- a/devices/src/virtio/descriptor_utils.rs +++ b/devices/src/virtio/descriptor_utils.rs @@ -216,8 +216,8 @@ pub struct Reader<'a> { buffer: DescriptorChainConsumer<'a>, } -// An iterator over `DataInit` objects on readable descriptors in the descriptor chain. -struct ReaderIterator<'a, T: DataInit> { +/// An iterator over `DataInit` objects on readable descriptors in the descriptor chain. +pub struct ReaderIterator<'a, T: DataInit> { reader: &'a mut Reader<'a>, phantom: PhantomData<T>, } @@ -283,10 +283,17 @@ impl<'a> Reader<'a> { /// them as a collection. Returns an error if the size of the remaining data is indivisible by /// the size of an object of type `T`. pub fn collect<C: FromIterator<io::Result<T>>, T: DataInit>(&'a mut self) -> C { - C::from_iter(ReaderIterator { + C::from_iter(self.iter()) + } + + /// Creates an iterator for sequentially reading `DataInit` objects from the `Reader`. Unlike + /// `collect`, this doesn't consume all the remaining data in the `Reader` and doesn't require + /// the objects to be stored in a separate collection. + pub fn iter<T: DataInit>(&'a mut self) -> ReaderIterator<'a, T> { + ReaderIterator { reader: self, phantom: PhantomData, - }) + } } /// Reads data from the descriptor chain buffer into a file descriptor. diff --git a/devices/src/virtio/fs/filesystem.rs b/devices/src/virtio/fs/filesystem.rs index 232ff99..eb9726c 100644 --- a/devices/src/virtio/fs/filesystem.rs +++ b/devices/src/virtio/fs/filesystem.rs @@ -1139,4 +1139,34 @@ pub trait FileSystem { fn lseek(&self) -> io::Result<()> { Err(io::Error::from_raw_os_error(libc::ENOSYS)) } + + /// Copy a range of data from one file to another + /// + /// Performs an optimized copy between two file descriptors without the additional cost of + /// transferring data through the kernel module to user space (glibc) and then back into + /// the file system again. + /// + /// In case this method is not implemented, glibc falls back to reading data from the source and + /// writing to the destination. + /// + /// If this method fails with an `ENOSYS` error, then the kernel will treat that as a permanent + /// failure. The kernel will return `EOPNOTSUPP` for all future calls to `copy_file_range` + /// without forwarding them to the file system. + /// + /// All values accepted by the `copy_file_range(2)` system call are valid values for `flags` and + /// must be handled by the file system. + fn copy_file_range( + &self, + ctx: Context, + inode_src: Self::Inode, + handle_src: Self::Handle, + offset_src: u64, + inode_dst: Self::Inode, + handle_dst: Self::Handle, + offset_dst: u64, + length: u64, + flags: u64, + ) -> io::Result<usize> { + Err(io::Error::from_raw_os_error(libc::ENOSYS)) + } } diff --git a/devices/src/virtio/fs/fuse.rs b/devices/src/virtio/fs/fuse.rs index 612202b..5c531cf 100644 --- a/devices/src/virtio/fs/fuse.rs +++ b/devices/src/virtio/fs/fuse.rs @@ -12,8 +12,11 @@ use libc; /// Version number of this interface. pub const KERNEL_VERSION: u32 = 7; +/// Oldest supported minor version of the fuse interface. +pub const OLDEST_SUPPORTED_KERNEL_MINOR_VERSION: u32 = 27; + /// Minor version number of this interface. -pub const KERNEL_MINOR_VERSION: u32 = 27; +pub const KERNEL_MINOR_VERSION: u32 = 31; /// The ID of the inode corresponding to the root directory of the file system. pub const ROOT_ID: u64 = 1; @@ -56,6 +59,12 @@ const FOPEN_KEEP_CACHE: u32 = 2; /// The file is not seekable. const FOPEN_NONSEEKABLE: u32 = 4; +/// Allow caching the directory entries. +const FOPEN_CACHE_DIR: u32 = 8; + +/// This file is stream-like (i.e., no file position). +const FOPEN_STREAM: u32 = 16; + bitflags! { /// Options controlling the behavior of files opened by the server in response /// to an open or create request. @@ -63,6 +72,8 @@ bitflags! { const DIRECT_IO = FOPEN_DIRECT_IO; const KEEP_CACHE = FOPEN_KEEP_CACHE; const NONSEEKABLE = FOPEN_NONSEEKABLE; + const CACHE_DIR = FOPEN_CACHE_DIR; + const STREAM = FOPEN_STREAM; } } @@ -131,6 +142,24 @@ const HANDLE_KILLPRIV: u32 = 524288; /// FileSystem supports posix acls. const POSIX_ACL: u32 = 1048576; +/// Reading the device after an abort returns `ECONNABORTED`. +const ABORT_ERROR: u32 = 2097152; + +/// The reply to the `init` message contains the max number of request pages. +const MAX_PAGES: u32 = 4194304; + +/// Cache `readlink` responses. +const CACHE_SYMLINKS: u32 = 8388608; + +/// Kernel supports zero-message opens for directories. +const NO_OPENDIR_SUPPORT: u32 = 16777216; + +/// Kernel supports explicit cache invalidation. +const EXPLICIT_INVAL_DATA: u32 = 33554432; + +/// The `map_alignment` field of the `InitOut` struct is valid. +const MAP_ALIGNMENT: u32 = 67108864; + bitflags! { /// A bitfield passed in as a parameter to and returned from the `init` method of the /// `FileSystem` trait. @@ -297,6 +326,37 @@ bitflags! { /// /// This feature is disabled by default. const POSIX_ACL = POSIX_ACL; + + /// Indicates that the kernel may cache responses to `readlink` calls. + const CACHE_SYMLINKS = CACHE_SYMLINKS; + + /// Indicates support for zero-message opens for directories. If this flag is set in the + /// `capable` parameter of the `init` trait method, then the file system may return `ENOSYS` + /// from the opendir() handler to indicate success. Further attempts to open directories + /// will be handled in the kernel. (If this flag is not set, returning ENOSYS will be + /// treated as an error and signaled to the caller). + /// + /// Setting (or not setting) the field in the `FsOptions` returned from the `init` method + /// has no effect. + const ZERO_MESSAGE_OPENDIR = NO_OPENDIR_SUPPORT; + + /// Indicates support for invalidating cached pages only on explicit request. + /// + /// If this flag is set in the `capable` parameter of the `init` trait method, then the FUSE + /// kernel module supports invalidating cached pages only on explicit request by the + /// filesystem. + /// + /// By setting this flag in the return value of the `init` trait method, the filesystem is + /// responsible for invalidating cached pages through explicit requests to the kernel. + /// + /// Note that setting this flag does not prevent the cached pages from being flushed by OS + /// itself and/or through user actions. + /// + /// Note that if both EXPLICIT_INVAL_DATA and AUTO_INVAL_DATA are set in the `capable` + /// parameter of the `init` trait method then AUTO_INVAL_DATA takes precedence. + /// + /// This feature is disabled by default. + const EXPLICIT_INVAL_DATA = EXPLICIT_INVAL_DATA; } } @@ -318,6 +378,9 @@ pub const WRITE_CACHE: u32 = 1; /// `lock_owner` field is valid. pub const WRITE_LOCKOWNER: u32 = 2; +/// Kill the suid and sgid bits. +pub const WRITE_KILL_PRIV: u32 = 3; + // Read flags. pub const READ_LOCKOWNER: u32 = 2; @@ -338,6 +401,9 @@ const IOCTL_32BIT: u32 = 8; /// Is a directory const IOCTL_DIR: u32 = 16; +/// 32-bit compat ioctl on 64-bit machine with 64-bit time_t. +const IOCTL_COMPAT_X32: u32 = 32; + /// Maximum of in_iovecs + out_iovecs pub const IOCTL_MAX_IOV: usize = 256; @@ -357,6 +423,9 @@ bitflags! { /// Is a directory const DIR = IOCTL_DIR; + + /// 32-bit compat ioctl on 64-bit machine with 64-bit time_t. + const COMPAT_X32 = IOCTL_COMPAT_X32; } } @@ -511,6 +580,9 @@ pub enum Opcode { Readdirplus = 44, Rename2 = 45, Lseek = 46, + CopyFileRange = 47, + SetUpMapping = 48, + RemoveMapping = 49, } #[repr(u32)] @@ -830,7 +902,9 @@ pub struct InitOut { pub congestion_threshold: u16, pub max_write: u32, pub time_gran: u32, - pub unused: [u32; 9], + pub max_pages: u16, + pub map_alignment: u16, + pub unused: [u32; 8], } unsafe impl DataInit for InitOut {} @@ -1049,3 +1123,16 @@ pub struct LseekOut { pub offset: u64, } unsafe impl DataInit for LseekOut {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct CopyFileRangeIn { + pub fh_src: u64, + pub off_src: u64, + pub nodeid_dst: u64, + pub fh_dst: u64, + pub off_dst: u64, + pub len: u64, + pub flags: u64, +} +unsafe impl DataInit for CopyFileRangeIn {} diff --git a/devices/src/virtio/fs/passthrough.rs b/devices/src/virtio/fs/passthrough.rs index bdc1c6f..419d466 100644 --- a/devices/src/virtio/fs/passthrough.rs +++ b/devices/src/virtio/fs/passthrough.rs @@ -395,7 +395,7 @@ impl PassthroughFs { libc::openat( self.proc.as_raw_fd(), pathname.as_ptr(), - (flags | libc::O_CLOEXEC) & (!libc::O_NOFOLLOW), + (flags | libc::O_CLOEXEC) & !(libc::O_NOFOLLOW | libc::O_DIRECT), ) }; if fd < 0 { @@ -592,7 +592,13 @@ impl PassthroughFs { OpenOptions::DIRECT_IO, flags & (libc::O_DIRECTORY as u32) == 0, ), - CachePolicy::Always => opts |= OpenOptions::KEEP_CACHE, + CachePolicy::Always => { + opts |= if flags & (libc::O_DIRECTORY as u32) == 0 { + OpenOptions::KEEP_CACHE + } else { + OpenOptions::CACHE_DIR + } + } _ => {} }; @@ -965,7 +971,8 @@ impl FileSystem for PassthroughFs { libc::openat( data.file.as_raw_fd(), name.as_ptr(), - flags as i32 | libc::O_CREAT | libc::O_CLOEXEC | libc::O_NOFOLLOW, + (flags as i32 | libc::O_CREAT | libc::O_CLOEXEC | libc::O_NOFOLLOW) + & !libc::O_DIRECT, mode & !(umask & 0o777), ) }; @@ -1689,4 +1696,58 @@ impl FileSystem for PassthroughFs { Err(io::Error::from_raw_os_error(libc::ENOTTY)) } } + + fn copy_file_range( + &self, + ctx: Context, + inode_src: Inode, + handle_src: Handle, + offset_src: u64, + inode_dst: Inode, + handle_dst: Handle, + offset_dst: u64, + length: u64, + flags: u64, + ) -> io::Result<usize> { + // We need to change credentials during a write so that the kernel will remove setuid or + // setgid bits from the file if it was written to by someone other than the owner. + let (_uid, _gid) = set_creds(ctx.uid, ctx.gid)?; + let src_data = self + .handles + .read() + .unwrap() + .get(&handle_src) + .filter(|hd| hd.inode == inode_src) + .map(Arc::clone) + .ok_or_else(ebadf)?; + let dst_data = self + .handles + .read() + .unwrap() + .get(&handle_dst) + .filter(|hd| hd.inode == inode_dst) + .map(Arc::clone) + .ok_or_else(ebadf)?; + + let src = src_data.file.lock().as_raw_fd(); + let dst = dst_data.file.lock().as_raw_fd(); + + let res = unsafe { + libc::syscall( + libc::SYS_copy_file_range, + src, + &offset_src, + dst, + offset_dst, + length, + flags, + ) + }; + + if res >= 0 { + Ok(res as usize) + } else { + Err(io::Error::last_os_error()) + } + } } diff --git a/devices/src/virtio/fs/server.rs b/devices/src/virtio/fs/server.rs index 32b5008..c9025f2 100644 --- a/devices/src/virtio/fs/server.rs +++ b/devices/src/virtio/fs/server.rs @@ -118,7 +118,8 @@ impl<F: FileSystem + Sync> Server<F> { Some(Opcode::Readdirplus) => self.readdirplus(in_header, r, w), Some(Opcode::Rename2) => self.rename2(in_header, r, w), Some(Opcode::Lseek) => self.lseek(in_header, r, w), - None => reply_error( + Some(Opcode::CopyFileRange) => self.copy_file_range(in_header, r, w), + Some(Opcode::SetUpMapping) | Some(Opcode::RemoveMapping) | None => reply_error( io::Error::from_raw_os_error(libc::ENOSYS), in_header.unique, w, @@ -809,7 +810,7 @@ impl<F: FileSystem + Sync> Server<F> { return reply_ok(Some(out), None, in_header.unique, w); } - if minor < KERNEL_MINOR_VERSION { + if minor < OLDEST_SUPPORTED_KERNEL_MINOR_VERSION { error!( "Unsupported fuse protocol minor version: {}.{}", major, minor @@ -1208,6 +1209,40 @@ impl<F: FileSystem + Sync> Server<F> { Ok(0) } } + + fn copy_file_range(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> { + let CopyFileRangeIn { + fh_src, + off_src, + nodeid_dst, + fh_dst, + off_dst, + len, + flags, + } = r.read_obj().map_err(Error::DecodeMessage)?; + + match self.fs.copy_file_range( + Context::from(in_header), + in_header.nodeid.into(), + fh_src.into(), + off_src, + nodeid_dst.into(), + fh_dst.into(), + off_dst, + len, + flags, + ) { + Ok(count) => { + let out = WriteOut { + size: count as u32, + ..Default::default() + }; + + reply_ok(Some(out), None, in_header.unique, w) + } + Err(e) => reply_error(e, in_header.unique, w), + } + } } fn retry_ioctl( diff --git a/devices/src/virtio/gpu/mod.rs b/devices/src/virtio/gpu/mod.rs index 8cc211f..93d004f 100644 --- a/devices/src/virtio/gpu/mod.rs +++ b/devices/src/virtio/gpu/mod.rs @@ -277,30 +277,24 @@ trait Backend { GpuResponse::ErrUnspec } - fn allocation_metadata( - &mut self, - _request_id: u32, - _request: Vec<u8>, - mut _response: Vec<u8>, - ) -> GpuResponse { - GpuResponse::ErrUnspec - } - fn resource_create_v2( &mut self, _resource_id: u32, - _guest_memory_type: u32, - _guest_caching_type: u32, + _ctx_id: u32, + _flags: u32, _size: u64, - _pci_addr: u64, - _mem: &GuestMemory, + _memory_id: u64, _vecs: Vec<(GuestAddress, usize)>, - _args: Vec<u8>, + _mem: &GuestMemory, ) -> GpuResponse { GpuResponse::ErrUnspec } - fn resource_v2_unref(&mut self, _resource_id: u32) -> GpuResponse { + fn resource_map(&mut self, _resource_id: u32, _pci_addr: u64) -> GpuResponse { + GpuResponse::ErrUnspec + } + + fn resource_unmap(&mut self, _resource_id: u32) -> GpuResponse { GpuResponse::ErrUnspec } } @@ -474,19 +468,19 @@ impl Frontend { let available_bytes = reader.available_bytes(); if available_bytes != 0 { let entry_count = info.nr_entries.to_native() as usize; - let mut iovecs = Vec::with_capacity(entry_count); + let mut vecs = Vec::with_capacity(entry_count); for _ in 0..entry_count { match reader.read_obj::<virtio_gpu_mem_entry>() { Ok(entry) => { let addr = GuestAddress(entry.addr.to_native()); let len = entry.length.to_native() as usize; - iovecs.push((addr, len)) + vecs.push((addr, len)) } Err(_) => return GpuResponse::ErrUnspec, } } self.backend - .attach_backing(info.resource_id.to_native(), mem, iovecs) + .attach_backing(info.resource_id.to_native(), mem, vecs) } else { error!("missing data for command {:?}", cmd); GpuResponse::ErrUnspec @@ -610,78 +604,49 @@ impl Frontend { GpuResponse::OkNoData } } - GpuCommand::AllocationMetadata(info) => { - if reader.available_bytes() != 0 { - let id = info.request_id.to_native(); - let request_size = info.request_size.to_native(); - let response_size = info.response_size.to_native(); - if request_size > VIRTIO_GPU_MAX_BLOB_ARGUMENT_SIZE - || response_size > VIRTIO_GPU_MAX_BLOB_ARGUMENT_SIZE - { - return GpuResponse::ErrUnspec; - } - - let mut request_buf = vec![0; request_size as usize]; - let response_buf = vec![0; response_size as usize]; - if reader.read_exact(&mut request_buf[..]).is_ok() { - self.backend - .allocation_metadata(id, request_buf, response_buf) - } else { - GpuResponse::ErrInvalidParameter - } - } else { - GpuResponse::ErrUnspec - } - } GpuCommand::ResourceCreateV2(info) => { - if reader.available_bytes() != 0 { - let resource_id = info.resource_id.to_native(); - let guest_memory_type = info.guest_memory_type.to_native(); - let size = info.size.to_native(); - let guest_caching_type = info.guest_caching_type.to_native(); - let pci_addr = info.pci_addr.to_native(); - let entry_count = info.nr_entries.to_native(); - let args_size = info.args_size.to_native(); - if args_size > VIRTIO_GPU_MAX_BLOB_ARGUMENT_SIZE - || entry_count > VIRTIO_GPU_MAX_IOVEC_ENTRIES - { - return GpuResponse::ErrUnspec; - } - - let mut iovecs = Vec::with_capacity(entry_count as usize); - let mut args = vec![0; args_size as usize]; + let resource_id = info.resource_id.to_native(); + let ctx_id = info.hdr.ctx_id.to_native(); + let flags = info.flags.to_native(); + let size = info.size.to_native(); + let memory_id = info.memory_id.to_native(); + let entry_count = info.nr_entries.to_native(); + if entry_count > VIRTIO_GPU_MAX_IOVEC_ENTRIES + || (reader.available_bytes() == 0 && entry_count > 0) + { + return GpuResponse::ErrUnspec; + } - for _ in 0..entry_count { - match reader.read_obj::<virtio_gpu_mem_entry>() { - Ok(entry) => { - let addr = GuestAddress(entry.addr.to_native()); - let len = entry.length.to_native() as usize; - iovecs.push((addr, len)) - } - Err(_) => return GpuResponse::ErrUnspec, + let mut vecs = Vec::with_capacity(entry_count as usize); + for _ in 0..entry_count { + match reader.read_obj::<virtio_gpu_mem_entry>() { + Ok(entry) => { + let addr = GuestAddress(entry.addr.to_native()); + let len = entry.length.to_native() as usize; + vecs.push((addr, len)) } + Err(_) => return GpuResponse::ErrUnspec, } - - match reader.read_exact(&mut args[..]) { - Ok(_) => self.backend.resource_create_v2( - resource_id, - guest_memory_type, - guest_caching_type, - size, - pci_addr, - mem, - iovecs, - args, - ), - Err(_) => GpuResponse::ErrUnspec, - } - } else { - GpuResponse::ErrUnspec } + + self.backend.resource_create_v2( + resource_id, + ctx_id, + flags, + size, + memory_id, + vecs, + mem, + ) + } + GpuCommand::ResourceMap(info) => { + let resource_id = info.resource_id.to_native(); + let offset = info.offset.to_native(); + self.backend.resource_map(resource_id, offset) } - GpuCommand::ResourceV2Unref(info) => { + GpuCommand::ResourceUnmap(info) => { let resource_id = info.resource_id.to_native(); - self.backend.resource_v2_unref(resource_id) + self.backend.resource_unmap(resource_id) } } } diff --git a/devices/src/virtio/gpu/protocol.rs b/devices/src/virtio/gpu/protocol.rs index b4610b2..c98e289 100644 --- a/devices/src/virtio/gpu/protocol.rs +++ b/devices/src/virtio/gpu/protocol.rs @@ -19,9 +19,10 @@ use data_model::{DataInit, Le32, Le64}; pub const VIRTIO_GPU_F_VIRGL: u32 = 0; pub const VIRTIO_GPU_F_EDID: u32 = 1; /* The following capabilities are not upstreamed. */ -pub const VIRTIO_GPU_F_MEMORY: u32 = 2; -pub const VIRTIO_GPU_F_SHARED_GUEST: u32 = 3; -pub const VIRTIO_GPU_F_HOST_COHERENT: u32 = 4; +pub const VIRTIO_GPU_F_RESOURCE_UUID: u32 = 2; +pub const VIRTIO_GPU_F_RESOURCE_V2: u32 = 3; +pub const VIRTIO_GPU_F_HOST_VISIBLE: u32 = 4; +pub const VIRTIO_GPU_F_VULKAN: u32 = 5; pub const VIRTIO_GPU_UNDEFINED: u32 = 0x0; @@ -49,8 +50,8 @@ pub const VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D: u32 = 0x206; pub const VIRTIO_GPU_CMD_SUBMIT_3D: u32 = 0x207; /* The following hypercalls are not upstreamed. */ pub const VIRTIO_GPU_CMD_RESOURCE_CREATE_V2: u32 = 0x208; -pub const VIRTIO_GPU_CMD_RESOURCE_CREATE_V2_UNREF: u32 = 0x209; -pub const VIRTIO_GPU_CMD_ALLOCATION_METADATA: u32 = 0x20a; +pub const VIRTIO_GPU_CMD_RESOURCE_MAP: u32 = 0x209; +pub const VIRTIO_GPU_CMD_RESOURCE_UNMAP: u32 = 0x20a; /* cursor commands */ pub const VIRTIO_GPU_CMD_UPDATE_CURSOR: u32 = 0x300; @@ -63,7 +64,6 @@ pub const VIRTIO_GPU_RESP_OK_CAPSET_INFO: u32 = 0x1102; pub const VIRTIO_GPU_RESP_OK_CAPSET: u32 = 0x1103; pub const VIRTIO_GPU_RESP_OK_RESOURCE_PLANE_INFO: u32 = 0x1104; pub const VIRTIO_GPU_RESP_OK_EDID: u32 = 0x1105; -pub const VIRTIO_GPU_RESP_OK_ALLOCATION_METADATA: u32 = 0x1106; /* error responses */ pub const VIRTIO_GPU_RESP_ERR_UNSPEC: u32 = 0x1200; @@ -73,20 +73,22 @@ pub const VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID: u32 = 0x1203; pub const VIRTIO_GPU_RESP_ERR_INVALID_CONTEXT_ID: u32 = 0x1204; pub const VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER: u32 = 0x1205; -/* guest memory types (not upstreamed) */ -pub const VIRTIO_GPU_MEMORY_UNDEFINED: u32 = 0; -pub const VIRTIO_GPU_MEMORY_TRANSFER: u32 = 1; -pub const VIRTIO_GPU_MEMORY_SHARED_GUEST: u32 = 2; -pub const VIRTIO_GPU_MEMORY_HOST_COHERENT: u32 = 3; +/* Resource flags (not upstreamed) */ +pub const VIRTIO_GPU_RESOURCE_GUEST_NONE: u32 = 0x0000; +pub const VIRTIO_GPU_RESOURCE_GUEST_MASK: u32 = 0x000f; +pub const VIRTIO_GPU_RESOURCE_GUEST_SYSTEM: u32 = 0x0001; -/* guest caching types (not upstreamed) */ -pub const VIRTIO_GPU_UNDEFINED_CACHING: u32 = 0; -pub const VIRTIO_GPU_CACHED: u32 = 1; -pub const VIRTIO_GPU_WRITE_COMBINE: u32 = 2; -pub const VIRTIO_GPU_UNCACHED: u32 = 3; +pub const VIRTIO_GPU_RESOURCE_HOST_NONE: u32 = 0x0000; +pub const VIRTIO_GPU_RESOURCE_HOST_MASK: u32 = 0x00f0; +pub const VIRTIO_GPU_RESOURCE_HOST: u32 = 0x0010; +pub const VIRTIO_GPU_RESOURCE_HOST_FROM_GUEST: u32 = 0x0020; + +pub const VIRTIO_GPU_RESOURCE_USE_NONE: u32 = 0x0000; +pub const VIRTIO_GPU_RESOURCE_USE_MASK: u32 = 0x0f00; +pub const VIRTIO_GPU_RESOURCE_USE_MAPPABLE: u32 = 0x0100; +pub const VIRTIO_GPU_RESOURCE_USE_SHAREABLE: u32 = 0x0200; +pub const VIRTIO_GPU_RESOURCE_USE_CROSS_DEVICE: u32 = 0x0400; -/* Limits on virtio-gpu stream (not upstreamed) */ -pub const VIRTIO_GPU_MAX_BLOB_ARGUMENT_SIZE: u32 = 4096; /* This matches the limit in udmabuf.c */ pub const VIRTIO_GPU_MAX_IOVEC_ENTRIES: u32 = 1024; @@ -112,8 +114,8 @@ pub fn virtio_gpu_cmd_str(cmd: u32) -> &'static str { VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D => "VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D", VIRTIO_GPU_CMD_SUBMIT_3D => "VIRTIO_GPU_CMD_SUBMIT_3D", VIRTIO_GPU_CMD_RESOURCE_CREATE_V2 => "VIRTIO_GPU_CMD_RESOURCE_CREATE_V2", - VIRTIO_GPU_CMD_RESOURCE_CREATE_V2_UNREF => "VIRTIO_GPU_CMD_RESOURCE_CREATE_V2_UNREF", - VIRTIO_GPU_CMD_ALLOCATION_METADATA => "VIRTIO_GPU_CMD_ALLOCATION_METADATA", + VIRTIO_GPU_CMD_RESOURCE_MAP => "VIRTIO_GPU_RESOURCE_MAP", + VIRTIO_GPU_CMD_RESOURCE_UNMAP => "VIRTIO_GPU_RESOURCE_UNMAP", VIRTIO_GPU_CMD_UPDATE_CURSOR => "VIRTIO_GPU_CMD_UPDATE_CURSOR", VIRTIO_GPU_CMD_MOVE_CURSOR => "VIRTIO_GPU_CMD_MOVE_CURSOR", VIRTIO_GPU_RESP_OK_NODATA => "VIRTIO_GPU_RESP_OK_NODATA", @@ -497,52 +499,38 @@ unsafe impl DataInit for virtio_gpu_config {} #[derive(Copy, Clone, Debug, Default)] #[repr(C)] -pub struct virtio_gpu_allocation_metadata { +pub struct virtio_gpu_resource_create_v2 { pub hdr: virtio_gpu_ctrl_hdr, - pub request_id: Le32, + pub resource_id: Le32, + pub flags: Le32, + pub size: Le64, + pub memory_id: Le64, + pub nr_entries: Le32, pub padding: Le32, - pub request_size: Le32, - pub response_size: Le32, } -unsafe impl DataInit for virtio_gpu_allocation_metadata {} - -/* VIRTIO_GPU_RESP_OK_ALLOCATION_METADATA */ -#[derive(Copy, Clone, Debug, Default)] -#[repr(C)] -pub struct virtio_gpu_resp_allocation_metadata { - pub hdr: virtio_gpu_ctrl_hdr, - pub request_id: Le32, - pub response_size: Le32, -} - -unsafe impl DataInit for virtio_gpu_resp_allocation_metadata {} +unsafe impl DataInit for virtio_gpu_resource_create_v2 {} #[derive(Copy, Clone, Debug, Default)] #[repr(C)] -pub struct virtio_gpu_resource_create_v2 { +pub struct virtio_gpu_resource_map { pub hdr: virtio_gpu_ctrl_hdr, pub resource_id: Le32, - pub guest_memory_type: Le32, - pub guest_caching_type: Le32, - pub padding: Le32, - pub size: Le64, - pub pci_addr: Le64, - pub args_size: Le32, - pub nr_entries: Le32, + pub map_flags: Le32, + pub offset: Le64, } -unsafe impl DataInit for virtio_gpu_resource_create_v2 {} +unsafe impl DataInit for virtio_gpu_resource_map {} #[derive(Copy, Clone, Debug, Default)] #[repr(C)] -pub struct virtio_gpu_resource_v2_unref { +pub struct virtio_gpu_resource_unmap { pub hdr: virtio_gpu_ctrl_hdr, pub resource_id: Le32, pub padding: Le32, } -unsafe impl DataInit for virtio_gpu_resource_v2_unref {} +unsafe impl DataInit for virtio_gpu_resource_unmap {} /* simple formats for fbcon/X use */ pub const VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM: u32 = 1; @@ -576,8 +564,8 @@ pub enum GpuCommand { TransferFromHost3d(virtio_gpu_transfer_host_3d), CmdSubmit3d(virtio_gpu_cmd_submit), ResourceCreateV2(virtio_gpu_resource_create_v2), - ResourceV2Unref(virtio_gpu_resource_v2_unref), - AllocationMetadata(virtio_gpu_allocation_metadata), + ResourceMap(virtio_gpu_resource_map), + ResourceUnmap(virtio_gpu_resource_unmap), UpdateCursor(virtio_gpu_update_cursor), MoveCursor(virtio_gpu_update_cursor), } @@ -645,8 +633,8 @@ impl fmt::Debug for GpuCommand { TransferFromHost3d(_info) => f.debug_struct("TransferFromHost3d").finish(), CmdSubmit3d(_info) => f.debug_struct("CmdSubmit3d").finish(), ResourceCreateV2(_info) => f.debug_struct("ResourceCreateV2").finish(), - ResourceV2Unref(_info) => f.debug_struct("ResourceV2Unref").finish(), - AllocationMetadata(_info) => f.debug_struct("AllocationMetadata").finish(), + ResourceMap(_info) => f.debug_struct("ResourceMap").finish(), + ResourceUnmap(_info) => f.debug_struct("ResourceUnmap").finish(), UpdateCursor(_info) => f.debug_struct("UpdateCursor").finish(), MoveCursor(_info) => f.debug_struct("MoveCursor").finish(), } @@ -678,8 +666,8 @@ impl GpuCommand { VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D => TransferFromHost3d(cmd.read_obj()?), VIRTIO_GPU_CMD_SUBMIT_3D => CmdSubmit3d(cmd.read_obj()?), VIRTIO_GPU_CMD_RESOURCE_CREATE_V2 => ResourceCreateV2(cmd.read_obj()?), - VIRTIO_GPU_CMD_RESOURCE_CREATE_V2_UNREF => ResourceV2Unref(cmd.read_obj()?), - VIRTIO_GPU_CMD_ALLOCATION_METADATA => AllocationMetadata(cmd.read_obj()?), + VIRTIO_GPU_CMD_RESOURCE_MAP => ResourceMap(cmd.read_obj()?), + VIRTIO_GPU_CMD_RESOURCE_UNMAP => ResourceUnmap(cmd.read_obj()?), VIRTIO_GPU_CMD_UPDATE_CURSOR => UpdateCursor(cmd.read_obj()?), VIRTIO_GPU_CMD_MOVE_CURSOR => MoveCursor(cmd.read_obj()?), _ => return Err(GpuCommandDecodeError::InvalidType(hdr.type_.into())), @@ -709,8 +697,8 @@ impl GpuCommand { TransferFromHost3d(info) => &info.hdr, CmdSubmit3d(info) => &info.hdr, ResourceCreateV2(info) => &info.hdr, - ResourceV2Unref(info) => &info.hdr, - AllocationMetadata(info) => &info.hdr, + ResourceMap(info) => &info.hdr, + ResourceUnmap(info) => &info.hdr, UpdateCursor(info) => &info.hdr, MoveCursor(info) => &info.hdr, } @@ -723,12 +711,6 @@ pub struct GpuResponsePlaneInfo { pub offset: u32, } -#[derive(Default, Debug, PartialEq)] -pub struct AllocationMetadataResponse { - pub request_id: u32, - pub response: Vec<u8>, -} - /// A response to a `GpuCommand`. These correspond to `VIRTIO_GPU_RESP_*`. #[derive(Debug, PartialEq)] pub enum GpuResponse { @@ -744,9 +726,6 @@ pub enum GpuResponse { format_modifier: u64, plane_info: Vec<GpuResponsePlaneInfo>, }, - OkAllocationMetadata { - res_info: AllocationMetadataResponse, - }, ErrUnspec, ErrOutOfMemory, ErrInvalidScanoutId, @@ -880,17 +859,6 @@ impl GpuResponse { size_of_val(&hdr) } } - GpuResponse::OkAllocationMetadata { ref res_info } => { - let resp_info = virtio_gpu_resp_allocation_metadata { - hdr, - request_id: Le32::from(res_info.request_id), - response_size: Le32::from(res_info.response.len() as u32), - }; - - resp.write_obj(resp_info)?; - resp.write_all(&res_info.response)?; - size_of_val(&resp_info) + res_info.response.len() - } _ => { resp.write_obj(hdr)?; size_of_val(&hdr) @@ -907,7 +875,6 @@ impl GpuResponse { GpuResponse::OkCapsetInfo { .. } => VIRTIO_GPU_RESP_OK_CAPSET_INFO, GpuResponse::OkCapset(_) => VIRTIO_GPU_RESP_OK_CAPSET, GpuResponse::OkResourcePlaneInfo { .. } => VIRTIO_GPU_RESP_OK_RESOURCE_PLANE_INFO, - GpuResponse::OkAllocationMetadata { .. } => VIRTIO_GPU_RESP_OK_ALLOCATION_METADATA, GpuResponse::ErrUnspec => VIRTIO_GPU_RESP_ERR_UNSPEC, GpuResponse::ErrOutOfMemory => VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY, GpuResponse::ErrInvalidScanoutId => VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID, @@ -925,7 +892,6 @@ impl GpuResponse { GpuResponse::OkCapsetInfo { .. } => true, GpuResponse::OkCapset(_) => true, GpuResponse::OkResourcePlaneInfo { .. } => true, - GpuResponse::OkAllocationMetadata { .. } => true, _ => false, } } diff --git a/devices/src/virtio/gpu/virtio_3d_backend.rs b/devices/src/virtio/gpu/virtio_3d_backend.rs index 7ae044c..3ede8a6 100644 --- a/devices/src/virtio/gpu/virtio_3d_backend.rs +++ b/devices/src/virtio/gpu/virtio_3d_backend.rs @@ -26,13 +26,13 @@ use gpu_renderer::{ }; use super::protocol::{ - AllocationMetadataResponse, GpuResponse, GpuResponsePlaneInfo, VIRTIO_GPU_CAPSET3, - VIRTIO_GPU_CAPSET_VIRGL, VIRTIO_GPU_CAPSET_VIRGL2, VIRTIO_GPU_MEMORY_HOST_COHERENT, + GpuResponse, GpuResponsePlaneInfo, VIRTIO_GPU_CAPSET3, VIRTIO_GPU_CAPSET_VIRGL, + VIRTIO_GPU_CAPSET_VIRGL2, VIRTIO_GPU_RESOURCE_USE_MAPPABLE, VIRTIO_GPU_RESOURCE_USE_MASK, }; pub use crate::virtio::gpu::virtio_backend::{VirtioBackend, VirtioResource}; use crate::virtio::gpu::{ - Backend, DisplayBackend, VIRTIO_F_VERSION_1, VIRTIO_GPU_F_HOST_COHERENT, VIRTIO_GPU_F_MEMORY, - VIRTIO_GPU_F_VIRGL, + Backend, DisplayBackend, VIRTIO_F_VERSION_1, VIRTIO_GPU_F_HOST_VISIBLE, + VIRTIO_GPU_F_RESOURCE_UUID, VIRTIO_GPU_F_RESOURCE_V2, VIRTIO_GPU_F_VIRGL, VIRTIO_GPU_F_VULKAN, }; use crate::virtio::resource_bridge::{PlaneInfo, ResourceInfo, ResourceResponse}; @@ -44,6 +44,8 @@ struct Virtio3DResource { gpu_resource: GpuRendererResource, display_import: Option<(Rc<RefCell<GpuDisplay>>, u32)>, kvm_slot: Option<u32>, + size: u64, + flags: u32, } impl Virtio3DResource { @@ -54,27 +56,38 @@ impl Virtio3DResource { gpu_resource, display_import: None, kvm_slot: None, + flags: 0, + // The size of the host resource isn't really zero, but it's undefined by + // virtio_gpu_resource_create_3d + size: 0, } } pub fn v2_new( width: u32, height: u32, - kvm_slot: u32, gpu_resource: GpuRendererResource, + flags: u32, + size: u64, ) -> Virtio3DResource { Virtio3DResource { width, height, gpu_resource, display_import: None, - kvm_slot: Some(kvm_slot), + kvm_slot: None, + flags, + size, } } fn as_mut(&mut self) -> &mut dyn VirtioResource { self } + + fn use_flags(&self) -> u32 { + self.flags & VIRTIO_GPU_RESOURCE_USE_MASK + } } impl VirtioResource for Virtio3DResource { @@ -225,8 +238,10 @@ impl Backend for Virtio3DBackend { fn features() -> u64 { 1 << VIRTIO_GPU_F_VIRGL | 1 << VIRTIO_F_VERSION_1 - | 1 << VIRTIO_GPU_F_MEMORY - | 1 << VIRTIO_GPU_F_HOST_COHERENT + | 1 << VIRTIO_GPU_F_RESOURCE_UUID + | 1 << VIRTIO_GPU_F_RESOURCE_V2 + | 1 << VIRTIO_GPU_F_HOST_VISIBLE + | 1 << VIRTIO_GPU_F_VULKAN } /// Returns the underlying Backend. @@ -757,51 +772,26 @@ impl Backend for Virtio3DBackend { } } - fn allocation_metadata( - &mut self, - request_id: u32, - request: Vec<u8>, - mut response: Vec<u8>, - ) -> GpuResponse { - let res = self.renderer.allocation_metadata(&request, &mut response); - - match res { - Ok(_) => { - let res_info = AllocationMetadataResponse { - request_id, - response, - }; - - GpuResponse::OkAllocationMetadata { res_info } - } - Err(_) => { - error!("failed to get metadata"); - GpuResponse::ErrUnspec - } - } - } - fn resource_create_v2( &mut self, resource_id: u32, - guest_memory_type: u32, - guest_caching_type: u32, + ctx_id: u32, + flags: u32, size: u64, - pci_addr: u64, - mem: &GuestMemory, + memory_id: u64, vecs: Vec<(GuestAddress, usize)>, - args: Vec<u8>, + mem: &GuestMemory, ) -> GpuResponse { match self.resources.entry(resource_id) { Entry::Vacant(entry) => { let resource = match self.renderer.resource_create_v2( resource_id, - guest_memory_type, - guest_caching_type, + ctx_id, + flags, size, - mem, + memory_id, &vecs, - &args, + mem, ) { Ok(resource) => resource, Err(e) => { @@ -810,102 +800,119 @@ impl Backend for Virtio3DBackend { } }; - match guest_memory_type { - VIRTIO_GPU_MEMORY_HOST_COHERENT => { - let dma_buf_fd = match resource.export() { - Ok(export) => export.1, - Err(e) => { - error!("failed to export plane fd: {}", e); - return GpuResponse::ErrUnspec; - } - }; - - let request = VmMemoryRequest::RegisterMemoryAtAddress( - self.pci_bar, - MaybeOwnedFd::Borrowed(dma_buf_fd.as_raw_fd()), - size as usize, - pci_addr, - ); - - match self.gpu_device_socket.send(&request) { - Ok(_resq) => match self.gpu_device_socket.recv() { - Ok(response) => match response { - VmMemoryResponse::RegisterMemory { pfn: _, slot } => { - entry.insert(Virtio3DResource::v2_new( - self.base.display_width, - self.base.display_height, - slot, - resource, - )); - GpuResponse::OkNoData - } - VmMemoryResponse::Err(e) => { - error!("received an error: {}", e); - GpuResponse::ErrUnspec - } - _ => { - error!("recieved an unexpected response"); - GpuResponse::ErrUnspec - } - }, - Err(e) => { - error!("failed to receive data: {}", e); - GpuResponse::ErrUnspec - } - }, - Err(e) => { - error!("failed to send request: {}", e); - GpuResponse::ErrUnspec - } - } - } - _ => { - entry.insert(Virtio3DResource::new( - self.base.display_width, - self.base.display_height, - resource, - )); + entry.insert(Virtio3DResource::v2_new( + self.base.display_width, + self.base.display_height, + resource, + flags, + size, + )); - GpuResponse::OkNoData - } - } + GpuResponse::OkNoData } Entry::Occupied(_) => GpuResponse::ErrInvalidResourceId, } } - fn resource_v2_unref(&mut self, resource_id: u32) -> GpuResponse { - match self.resources.remove(&resource_id) { - Some(entry) => match entry.kvm_slot { - Some(kvm_slot) => { - let request = VmMemoryRequest::UnregisterMemory(kvm_slot); - match self.gpu_device_socket.send(&request) { - Ok(_resq) => match self.gpu_device_socket.recv() { - Ok(response) => match response { - VmMemoryResponse::Ok => GpuResponse::OkNoData, - VmMemoryResponse::Err(e) => { - error!("received an error: {}", e); - GpuResponse::ErrUnspec - } - _ => { - error!("recieved an unexpected response"); - GpuResponse::ErrUnspec - } - }, - Err(e) => { - error!("failed to receive data: {}", e); - GpuResponse::ErrUnspec - } - }, - Err(e) => { - error!("failed to send request: {}", e); - GpuResponse::ErrUnspec - } - } - } - None => GpuResponse::OkNoData, - }, - None => GpuResponse::ErrInvalidResourceId, + fn resource_map(&mut self, resource_id: u32, offset: u64) -> GpuResponse { + let resource = match self.resources.get_mut(&resource_id) { + Some(r) => r, + None => return GpuResponse::ErrInvalidResourceId, + }; + + if resource.use_flags() & VIRTIO_GPU_RESOURCE_USE_MAPPABLE == 0 { + error!("resource not mappable"); + return GpuResponse::ErrUnspec; + } + + let dma_buf_fd = match resource.gpu_resource.export() { + Ok(export) => export.1, + Err(e) => { + error!("failed to export plane fd: {}", e); + return GpuResponse::ErrUnspec; + } + }; + + let request = VmMemoryRequest::RegisterFdAtPciBarOffset( + self.pci_bar, + MaybeOwnedFd::Borrowed(dma_buf_fd.as_raw_fd()), + resource.size as usize, + offset, + ); + + match self.gpu_device_socket.send(&request) { + Ok(_) => (), + Err(e) => { + error!("failed to send request: {}", e); + return GpuResponse::ErrUnspec; + } + } + + let response = match self.gpu_device_socket.recv() { + Ok(response) => response, + Err(e) => { + error!("failed to receive data: {}", e); + return GpuResponse::ErrUnspec; + } + }; + + match response { + VmMemoryResponse::RegisterMemory { pfn: _, slot } => { + resource.kvm_slot = Some(slot); + GpuResponse::OkNoData + } + VmMemoryResponse::Err(e) => { + error!("received an error: {}", e); + GpuResponse::ErrUnspec + } + _ => { + error!("recieved an unexpected response"); + GpuResponse::ErrUnspec + } + } + } + + fn resource_unmap(&mut self, resource_id: u32) -> GpuResponse { + let resource = match self.resources.get_mut(&resource_id) { + Some(r) => r, + None => return GpuResponse::ErrInvalidResourceId, + }; + + let kvm_slot = match resource.kvm_slot { + Some(kvm_slot) => kvm_slot, + None => return GpuResponse::ErrUnspec, + }; + + let request = VmMemoryRequest::UnregisterMemory(kvm_slot); + match self.gpu_device_socket.send(&request) { + Ok(_) => (), + Err(e) => { + error!("failed to send request: {}", e); + return GpuResponse::ErrUnspec; + } + } + + let response = match self.gpu_device_socket.recv() { + Ok(response) => response, + Err(e) => { + error!("failed to receive data: {}", e); + return GpuResponse::ErrUnspec; + } + }; + + match response { + VmMemoryResponse::Ok => { + resource.kvm_slot = None; + GpuResponse::OkNoData + } + VmMemoryResponse::Err(e) => { + error!("received an error: {}", e); + GpuResponse::ErrUnspec + } + _ => { + error!("recieved an unexpected response"); + GpuResponse::ErrUnspec + } } } } diff --git a/devices/src/virtio/gpu/virtio_gfxstream_backend.rs b/devices/src/virtio/gpu/virtio_gfxstream_backend.rs index aa02e15..2a49da8 100644 --- a/devices/src/virtio/gpu/virtio_gfxstream_backend.rs +++ b/devices/src/virtio/gpu/virtio_gfxstream_backend.rs @@ -10,7 +10,6 @@ use std::cell::RefCell; use std::collections::btree_map::Entry; use std::collections::BTreeMap as Map; -use std::fs::File; use std::mem::transmute; use std::os::raw::{c_char, c_int, c_uchar, c_uint, c_void}; use std::panic; @@ -27,6 +26,7 @@ use vm_control::VmMemoryControlRequestSocket; use super::protocol::GpuResponse; pub use super::virtio_backend::{VirtioBackend, VirtioResource}; use crate::virtio::gpu::{Backend, DisplayBackend, VIRTIO_F_VERSION_1, VIRTIO_GPU_F_VIRGL}; +use crate::virtio::resource_bridge::ResourceResponse; // C definitions related to gfxstream // In gfxstream, only write_fence is used @@ -340,8 +340,8 @@ impl Backend for VirtioGfxStreamBackend { } /// If supported, export the resource with the given id to a file. - fn export_resource(&mut self, _id: u32) -> Option<File> { - None + fn export_resource(&mut self, _id: u32) -> ResourceResponse { + ResourceResponse::Invalid } /// Creates a fence with the given id that can be used to determine when the previous command diff --git a/devices/src/virtio/interrupt.rs b/devices/src/virtio/interrupt.rs index f808d84..91a3942 100644 --- a/devices/src/virtio/interrupt.rs +++ b/devices/src/virtio/interrupt.rs @@ -13,7 +13,7 @@ pub struct Interrupt { interrupt_status: Arc<AtomicUsize>, interrupt_evt: EventFd, interrupt_resample_evt: EventFd, - msix_config: Option<Arc<Mutex<MsixConfig>>>, + pub msix_config: Option<Arc<Mutex<MsixConfig>>>, config_msix_vector: u16, } diff --git a/devices/src/virtio/mod.rs b/devices/src/virtio/mod.rs index 7716fe0..4d5d2cb 100644 --- a/devices/src/virtio/mod.rs +++ b/devices/src/virtio/mod.rs @@ -6,6 +6,7 @@ mod balloon; mod block; +mod console; mod descriptor_utils; mod input; mod interrupt; @@ -29,6 +30,7 @@ pub mod vhost; pub use self::balloon::*; pub use self::block::*; +pub use self::console::*; pub use self::descriptor_utils::Error as DescriptorError; pub use self::descriptor_utils::*; #[cfg(feature = "gpu")] diff --git a/devices/src/virtio/vhost/control_socket.rs b/devices/src/virtio/vhost/control_socket.rs new file mode 100644 index 0000000..a1ccfaf --- /dev/null +++ b/devices/src/virtio/vhost/control_socket.rs @@ -0,0 +1,36 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use msg_socket::{MsgOnSocket, MsgSocket}; +use sys_util::Error as SysError; + +#[derive(MsgOnSocket, Debug)] +pub enum VhostDevRequest { + /// Mask or unmask all the MSI entries for a Virtio Vhost device. + MsixChanged, + /// Mask or unmask a MSI entry for a Virtio Vhost device. + MsixEntryChanged(usize), +} + +#[derive(MsgOnSocket, Debug)] +pub enum VhostDevResponse { + Ok, + Err(SysError), +} + +pub type VhostDevRequestSocket = MsgSocket<VhostDevRequest, VhostDevResponse>; +pub type VhostDevResponseSocket = MsgSocket<VhostDevResponse, VhostDevRequest>; + +/// Create control socket pair. This pair is used to communicate with the +/// virtio device process. +/// Mainly between the virtio and activate thread. +pub fn create_control_sockets() -> ( + Option<VhostDevRequestSocket>, + Option<VhostDevResponseSocket>, +) { + match msg_socket::pair::<VhostDevRequest, VhostDevResponse>() { + Ok((request, response)) => (Some(request), Some(response)), + _ => (None, None), + } +} diff --git a/devices/src/virtio/vhost/mod.rs b/devices/src/virtio/vhost/mod.rs index 66c62d0..86ed81e 100644 --- a/devices/src/virtio/vhost/mod.rs +++ b/devices/src/virtio/vhost/mod.rs @@ -12,10 +12,12 @@ use remain::sorted; use sys_util::Error as SysError; use vhost::Error as VhostError; +mod control_socket; mod net; mod vsock; mod worker; +pub use self::control_socket::*; pub use self::net::Net; pub use self::vsock::Vsock; diff --git a/devices/src/virtio/vhost/net.rs b/devices/src/virtio/vhost/net.rs index ff72970..542423a 100644 --- a/devices/src/virtio/vhost/net.rs +++ b/devices/src/virtio/vhost/net.rs @@ -14,9 +14,12 @@ use sys_util::{error, warn, EventFd, GuestMemory}; use vhost::NetT as VhostNetT; use virtio_sys::virtio_net; +use super::control_socket::*; use super::worker::Worker; use super::{Error, Result}; +use crate::pci::MsixStatus; use crate::virtio::{Interrupt, Queue, VirtioDevice, TYPE_NET}; +use msg_socket::{MsgReceiver, MsgSender}; const QUEUE_SIZE: u16 = 256; const NUM_QUEUES: usize = 2; @@ -31,6 +34,8 @@ pub struct Net<T: TapT, U: VhostNetT<T>> { vhost_interrupt: Option<Vec<EventFd>>, avail_features: u64, acked_features: u64, + request_socket: Option<VhostDevRequestSocket>, + response_socket: Option<VhostDevResponseSocket>, } impl<T, U> Net<T, U> @@ -85,6 +90,8 @@ where vhost_interrupt.push(EventFd::new().map_err(Error::VhostIrqCreate)?); } + let (request_socket, response_socket) = create_control_sockets(); + Ok(Net { workers_kill_evt: Some(kill_evt.try_clone().map_err(Error::CloneKillEventFd)?), kill_evt, @@ -94,6 +101,8 @@ where vhost_interrupt: Some(vhost_interrupt), avail_features, acked_features: 0u64, + request_socket, + response_socket, }) } } @@ -143,6 +152,14 @@ where } keep_fds.push(self.kill_evt.as_raw_fd()); + if let Some(request_socket) = &self.request_socket { + keep_fds.push(request_socket.as_raw_fd()); + } + + if let Some(response_socket) = &self.response_socket { + keep_fds.push(response_socket.as_raw_fd()); + } + keep_fds } @@ -189,6 +206,11 @@ where if let Some(vhost_interrupt) = self.vhost_interrupt.take() { if let Some(kill_evt) = self.workers_kill_evt.take() { let acked_features = self.acked_features; + let socket = if self.response_socket.is_some() { + self.response_socket.take() + } else { + None + }; let worker_result = thread::Builder::new() .name("vhost_net".to_string()) .spawn(move || { @@ -199,6 +221,7 @@ where interrupt, acked_features, kill_evt, + socket, ); let activate_vqs = |handle: &U| -> Result<()> { for idx in 0..NUM_QUEUES { @@ -251,6 +274,48 @@ where } } + fn control_notify(&self, behavior: MsixStatus) { + if self.worker_thread.is_none() || self.request_socket.is_none() { + return; + } + if let Some(socket) = &self.request_socket { + match behavior { + MsixStatus::EntryChanged(index) => { + if let Err(e) = socket.send(&VhostDevRequest::MsixEntryChanged(index)) { + error!( + "{} failed to send VhostMsixEntryChanged request for entry {}: {:?}", + self.debug_label(), + index, + e + ); + return; + } + if let Err(e) = socket.recv() { + error!("{} failed to receive VhostMsixEntryChanged response for entry {}: {:?}", self.debug_label(), index, e); + } + } + MsixStatus::Changed => { + if let Err(e) = socket.send(&VhostDevRequest::MsixChanged) { + error!( + "{} failed to send VhostMsixChanged request: {:?}", + self.debug_label(), + e + ); + return; + } + if let Err(e) = socket.recv() { + error!( + "{} failed to receive VhostMsixChanged response {:?}", + self.debug_label(), + e + ); + } + } + _ => {} + } + } + } + fn reset(&mut self) -> bool { // Only kill the child if it claimed its eventfd. if self.workers_kill_evt.is_none() && self.kill_evt.write(1).is_err() { @@ -269,6 +334,7 @@ where self.tap = Some(tap); self.vhost_interrupt = Some(worker.vhost_interrupt); self.workers_kill_evt = Some(worker.kill_evt); + self.response_socket = worker.response_socket; return true; } } diff --git a/devices/src/virtio/vhost/vsock.rs b/devices/src/virtio/vhost/vsock.rs index 03826ff..390c857 100644 --- a/devices/src/virtio/vhost/vsock.rs +++ b/devices/src/virtio/vhost/vsock.rs @@ -169,6 +169,7 @@ impl VirtioDevice for Vsock { interrupt, acked_features, kill_evt, + None, ); let activate_vqs = |handle: &VhostVsockHandle| -> Result<()> { handle.set_cid(cid).map_err(Error::VhostVsockSetCid)?; diff --git a/devices/src/virtio/vhost/worker.rs b/devices/src/virtio/vhost/worker.rs index 1eff01f..ca02a63 100644 --- a/devices/src/virtio/vhost/worker.rs +++ b/devices/src/virtio/vhost/worker.rs @@ -4,16 +4,16 @@ use std::os::raw::c_ulonglong; -use sys_util::{EventFd, PollContext, PollToken}; +use sys_util::{error, Error as SysError, EventFd, PollContext, PollToken}; use vhost::Vhost; +use super::control_socket::{VhostDevRequest, VhostDevResponse, VhostDevResponseSocket}; use super::{Error, Result}; use crate::virtio::{Interrupt, Queue}; +use libc::EIO; +use msg_socket::{MsgReceiver, MsgSender}; -/// Worker that takes care of running the vhost device. This mainly involves forwarding interrupts -/// from the vhost driver to the guest VM because crosvm only supports the virtio-mmio transport, -/// which requires a bit to be set in the interrupt status register before triggering the interrupt -/// and the vhost driver doesn't do this for us. +/// Worker that takes care of running the vhost device. pub struct Worker<T: Vhost> { interrupt: Interrupt, queues: Vec<Queue>, @@ -21,6 +21,7 @@ pub struct Worker<T: Vhost> { pub vhost_interrupt: Vec<EventFd>, acked_features: u64, pub kill_evt: EventFd, + pub response_socket: Option<VhostDevResponseSocket>, } impl<T: Vhost> Worker<T> { @@ -31,6 +32,7 @@ impl<T: Vhost> Worker<T> { interrupt: Interrupt, acked_features: u64, kill_evt: EventFd, + response_socket: Option<VhostDevResponseSocket>, ) -> Worker<T> { Worker { interrupt, @@ -39,6 +41,7 @@ impl<T: Vhost> Worker<T> { vhost_interrupt, acked_features, kill_evt, + response_socket, } } @@ -87,9 +90,7 @@ impl<T: Vhost> Worker<T> { self.vhost_handle .set_vring_base(queue_index, 0) .map_err(Error::VhostSetVringBase)?; - self.vhost_handle - .set_vring_call(queue_index, &self.vhost_interrupt[queue_index]) - .map_err(Error::VhostSetVringCall)?; + self.set_vring_call_for_entry(queue_index, queue.vector as usize)?; self.vhost_handle .set_vring_kick(queue_index, &queue_evts[queue_index]) .map_err(Error::VhostSetVringKick)?; @@ -102,6 +103,7 @@ impl<T: Vhost> Worker<T> { VhostIrqi { index: usize }, InterruptResample, Kill, + ControlNotify, } let poll_ctx: PollContext<Token> = PollContext::build_with(&[ @@ -115,6 +117,11 @@ impl<T: Vhost> Worker<T> { .add(vhost_int, Token::VhostIrqi { index }) .map_err(Error::CreatePollContext)?; } + if let Some(socket) = &self.response_socket { + poll_ctx + .add(socket, Token::ControlNotify) + .map_err(Error::CreatePollContext)?; + } 'poll: loop { let events = poll_ctx.wait().map_err(Error::PollError)?; @@ -134,10 +141,122 @@ impl<T: Vhost> Worker<T> { let _ = self.kill_evt.read(); break 'poll; } + Token::ControlNotify => { + if let Some(socket) = &self.response_socket { + match socket.recv() { + Ok(VhostDevRequest::MsixEntryChanged(index)) => { + let mut qindex = 0; + for (queue_index, queue) in self.queues.iter().enumerate() { + if queue.vector == index as u16 { + qindex = queue_index; + break; + } + } + let response = + match self.set_vring_call_for_entry(qindex, index) { + Ok(()) => VhostDevResponse::Ok, + Err(e) => { + error!( + "Set vring call failed for masked entry {}: {:?}", + index, e + ); + VhostDevResponse::Err(SysError::new(EIO)) + } + }; + if let Err(e) = socket.send(&response) { + error!("Vhost failed to send VhostMsixEntryMasked Response for entry {}: {:?}", index, e); + } + } + Ok(VhostDevRequest::MsixChanged) => { + let response = match self.set_vring_calls() { + Ok(()) => VhostDevResponse::Ok, + Err(e) => { + error!("Set vring calls failed: {:?}", e); + VhostDevResponse::Err(SysError::new(EIO)) + } + }; + if let Err(e) = socket.send(&response) { + error!( + "Vhost failed to send VhostMsixMasked Response: {:?}", + e + ); + } + } + Err(e) => { + error!("Vhost failed to receive Control request: {:?}", e); + } + } + } + } } } } cleanup_vqs(&self.vhost_handle)?; Ok(()) } + + fn set_vring_call_for_entry(&self, queue_index: usize, vector: usize) -> Result<()> { + // No response_socket means it doesn't have any control related + // with the msix. Due to this, cannot use the direct irq fd but + // should fall back to indirect irq fd. + if self.response_socket.is_some() { + if let Some(msix_config) = &self.interrupt.msix_config { + let msix_config = msix_config.lock(); + let msix_masked = msix_config.masked(); + if msix_masked { + return Ok(()); + } + if !msix_config.table_masked(vector) { + if let Some(irqfd) = msix_config.get_irqfd(vector) { + self.vhost_handle + .set_vring_call(queue_index, irqfd) + .map_err(Error::VhostSetVringCall)?; + } else { + self.vhost_handle + .set_vring_call(queue_index, &self.vhost_interrupt[queue_index]) + .map_err(Error::VhostSetVringCall)?; + } + return Ok(()); + } + } + } + + self.vhost_handle + .set_vring_call(queue_index, &self.vhost_interrupt[queue_index]) + .map_err(Error::VhostSetVringCall)?; + Ok(()) + } + + fn set_vring_calls(&self) -> Result<()> { + if let Some(msix_config) = &self.interrupt.msix_config { + let msix_config = msix_config.lock(); + if msix_config.masked() { + for (queue_index, _) in self.queues.iter().enumerate() { + self.vhost_handle + .set_vring_call(queue_index, &self.vhost_interrupt[queue_index]) + .map_err(Error::VhostSetVringCall)?; + } + } else { + for (queue_index, queue) in self.queues.iter().enumerate() { + let vector = queue.vector as usize; + if !msix_config.table_masked(vector) { + if let Some(irqfd) = msix_config.get_irqfd(vector) { + self.vhost_handle + .set_vring_call(queue_index, irqfd) + .map_err(Error::VhostSetVringCall)?; + } else { + self.vhost_handle + .set_vring_call(queue_index, &self.vhost_interrupt[queue_index]) + .map_err(Error::VhostSetVringCall)?; + } + } else { + self.vhost_handle + .set_vring_call(queue_index, &self.vhost_interrupt[queue_index]) + .map_err(Error::VhostSetVringCall)?; + } + } + } + } + Ok(()) + } } diff --git a/devices/src/virtio/virtio_device.rs b/devices/src/virtio/virtio_device.rs index 806a98f..6eb5548 100644 --- a/devices/src/virtio/virtio_device.rs +++ b/devices/src/virtio/virtio_device.rs @@ -7,7 +7,7 @@ use std::os::unix::io::RawFd; use sys_util::{EventFd, GuestMemory}; use super::*; -use crate::pci::{PciBarConfiguration, PciCapability}; +use crate::pci::{MsixStatus, PciBarConfiguration, PciCapability}; /// Trait for virtio devices to be driven by a virtio transport. /// @@ -86,4 +86,6 @@ pub trait VirtioDevice: Send { /// Invoked when the device is sandboxed. fn on_device_sandboxed(&mut self) {} + + fn control_notify(&self, _behavior: MsixStatus) {} } diff --git a/devices/src/virtio/virtio_pci_device.rs b/devices/src/virtio/virtio_pci_device.rs index c6d6786..e63abe9 100644 --- a/devices/src/virtio/virtio_pci_device.rs +++ b/devices/src/virtio/virtio_pci_device.rs @@ -535,7 +535,8 @@ impl PciDevice for VirtioPciDevice { fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]) { if let Some(msix_cap_reg_idx) = self.msix_cap_reg_idx { if msix_cap_reg_idx == reg_idx { - self.msix_config.lock().write_msix_capability(offset, data); + let behavior = self.msix_config.lock().write_msix_capability(offset, data); + self.device.control_notify(behavior); } } @@ -626,9 +627,11 @@ impl PciDevice for VirtioPciDevice { // Handled with ioeventfds. } o if MSIX_TABLE_BAR_OFFSET <= o && o < MSIX_TABLE_BAR_OFFSET + MSIX_TABLE_SIZE => { - self.msix_config + let behavior = self + .msix_config .lock() .write_msix_table(o - MSIX_TABLE_BAR_OFFSET, data); + self.device.control_notify(behavior); } o if MSIX_PBA_BAR_OFFSET <= o && o < MSIX_PBA_BAR_OFFSET + MSIX_PBA_SIZE => { self.msix_config |