diff options
65 files changed, 5064 insertions, 558 deletions
diff --git a/Cargo.lock b/Cargo.lock index 4df284d..7a9a5c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,7 @@ dependencies = [ name = "arch" version = "0.1.0" dependencies = [ + "acpi_tables 0.1.0", "devices 0.1.0", "io_jail 0.1.0", "kernel_cmdline 0.1.0", @@ -161,6 +162,7 @@ name = "data_model" version = "0.1.0" dependencies = [ "assertions 0.1.0", + "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -183,6 +185,7 @@ dependencies = [ "kvm_sys 0.1.0", "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", "libcras 0.1.0", + "libvda 0.1.0", "linux_input_sys 0.1.0", "msg_on_socket_derive 0.1.0", "msg_socket 0.1.0", @@ -347,9 +350,12 @@ dependencies = [ name = "hypervisor" version = "0.1.0" dependencies = [ + "bit_field 0.1.0", + "enumn 0.1.0", "kvm 0.1.0", "kvm_sys 0.1.0", "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", + "sync 0.1.0", "sys_util 0.1.0", ] @@ -412,6 +418,15 @@ dependencies = [ ] [[package]] +name = "libvda" +version = "0.1.0" +dependencies = [ + "enumn 0.1.0", + "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "linux_input_sys" version = "0.1.0" dependencies = [ diff --git a/Cargo.toml b/Cargo.toml index 3fdad6b..55f7df0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,8 @@ default-no-sandbox = [] gpu = ["devices/gpu"] plugin = ["protos/plugin", "crosvm_plugin", "protobuf"] tpm = ["devices/tpm"] +video-decoder = ["devices/video-decoder"] +video-encoder = ["devices/video-encoder"] wl-dmabuf = ["devices/wl-dmabuf", "gpu_buffer", "resources/wl-dmabuf"] x = ["devices/x"] virtio-gpu-next = ["gpu_renderer/virtio-gpu-next"] @@ -89,6 +91,7 @@ assertions = { path = "assertions" } audio_streams = { path = "../../third_party/adhd/audio_streams" } # ignored by ebuild data_model = { path = "data_model" } libcras = { path = "../../third_party/adhd/cras/client/libcras" } # ignored by ebuild +libvda = { path = "../../platform2/arc/vm/libvda/rust" } # ignored by ebuild minijail-sys = { path = "../../aosp/external/minijail" } # ignored by ebuild poll_token_derive = { path = "sys_util/poll_token_derive" } sync = { path = "sync" } diff --git a/acpi_tables/src/sdt.rs b/acpi_tables/src/sdt.rs index 0ea61a0..96f4d0f 100644 --- a/acpi_tables/src/sdt.rs +++ b/acpi_tables/src/sdt.rs @@ -2,6 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +use std::fs::File; +use std::io::{ErrorKind, Read, Result}; +use std::path::PathBuf; + use data_model::DataInit; /// SDT represents for System Description Table. The structure SDT is a @@ -54,6 +58,23 @@ impl SDT { sdt } + /// Set up the ACPI table from file content. Verify file checksum. + pub fn from_file(path: &PathBuf) -> Result<Self> { + let mut file = File::open(path)?; + let mut data = Vec::new(); + file.read_to_end(&mut data)?; + let checksum = super::generate_checksum(data.as_slice()); + if checksum == 0 { + Ok(SDT { data }) + } else { + Err(ErrorKind::InvalidData.into()) + } + } + + pub fn is_signature(&self, signature: &[u8; 4]) -> bool { + self.data[0..4] == *signature + } + fn update_checksum(&mut self) { self.data[CHECKSUM_OFFSET] = 0; let checksum = super::generate_checksum(self.data.as_slice()); diff --git a/arch/Cargo.toml b/arch/Cargo.toml index 6b4e529..68b40a3 100644 --- a/arch/Cargo.toml +++ b/arch/Cargo.toml @@ -5,6 +5,7 @@ authors = ["The Chromium OS Authors"] edition = "2018" [dependencies] +acpi_tables = { path = "../acpi_tables" } devices = { path = "../devices" } io_jail = { path = "../io_jail" } kernel_cmdline = { path = "../kernel_cmdline" } diff --git a/arch/src/android.rs b/arch/src/android.rs index 5311d3f..a3242ab 100644 --- a/arch/src/android.rs +++ b/arch/src/android.rs @@ -36,7 +36,7 @@ pub fn create_android_fdt(fdt: &mut Vec<u8>, fstab: File) -> Result<()> { begin_node(fdt, "vendor")?; for vec in dtprop { let content = std::fs::read_to_string(&vec[2]).map_err(Error::FdtIoError)?; - property_string(fdt, &vec[1], &content); + property_string(fdt, &vec[1], &content)?; } end_node(fdt)?; // vendor begin_node(fdt, "fstab")?; diff --git a/arch/src/lib.rs b/arch/src/lib.rs index 3445f18..bab679a 100644 --- a/arch/src/lib.rs +++ b/arch/src/lib.rs @@ -16,6 +16,7 @@ use std::os::unix::io::AsRawFd; use std::path::PathBuf; use std::sync::Arc; +use acpi_tables::sdt::SDT; use devices::split_irqchip_common::GsiRelay; use devices::virtio::VirtioDevice; use devices::{ @@ -57,6 +58,7 @@ pub struct VmComponents { pub initrd_image: Option<File>, pub extra_kernel_params: Vec<String>, pub wayland_dmabuf: bool, + pub acpi_sdts: Vec<SDT>, } /// Holds the elements needed to run a Linux VM. Created by `build_vm`. diff --git a/arch/src/serial.rs b/arch/src/serial.rs index f24f4bc..b817cfd 100644 --- a/arch/src/serial.rs +++ b/arch/src/serial.rs @@ -4,7 +4,7 @@ use std::collections::BTreeMap; use std::fmt::{self, Display}; -use std::fs::File; +use std::fs::{File, OpenOptions}; use std::io::{self, stdin, stdout}; use std::os::unix::io::{AsRawFd, RawFd}; use std::path::PathBuf; @@ -170,7 +170,11 @@ impl SerialParameters { } SerialType::File => match &self.path { Some(path) => { - let file = File::create(path.as_path()).map_err(Error::FileError)?; + let file = OpenOptions::new() + .append(true) + .create(true) + .open(path.as_path()) + .map_err(Error::FileError)?; keep_fds.push(file.as_raw_fd()); Some(Box::new(file)) } diff --git a/crosvm_plugin/src/lib.rs b/crosvm_plugin/src/lib.rs index 05c19bf..e9d135c 100644 --- a/crosvm_plugin/src/lib.rs +++ b/crosvm_plugin/src/lib.rs @@ -17,7 +17,7 @@ use std::env; use std::fs::File; -use std::io::{Read, Write}; +use std::io::{IoSlice, Read, Write}; use std::mem::{size_of, swap}; use std::os::raw::{c_int, c_void}; use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; @@ -290,7 +290,7 @@ impl crosvm { .write_to_vec(&mut self.request_buffer) .map_err(proto_error_to_int)?; self.socket - .send_with_fds(self.request_buffer.as_slice(), fds) + .send_with_fds(&[IoSlice::new(self.request_buffer.as_slice())], fds) .map_err(|e| -e.errno())?; let mut datagram_fds = [0; MAX_DATAGRAM_FD]; diff --git a/data_model/Cargo.toml b/data_model/Cargo.toml index b1447a6..3909956 100644 --- a/data_model/Cargo.toml +++ b/data_model/Cargo.toml @@ -7,5 +7,6 @@ include = ["src/**/*", "Cargo.toml"] [dependencies] assertions = { path = "../assertions" } # provided by ebuild +libc = "*" [workspace] diff --git a/data_model/src/volatile_memory.rs b/data_model/src/volatile_memory.rs index d834f0b..026be71 100644 --- a/data_model/src/volatile_memory.rs +++ b/data_model/src/volatile_memory.rs @@ -20,21 +20,25 @@ //! not reordered or elided the access. use std::cmp::min; -use std::fmt::{self, Display}; +use std::ffi::c_void; +use std::fmt::{self, Debug, Display}; use std::marker::PhantomData; use std::mem::size_of; use std::ptr::{copy, null_mut, read_volatile, write_bytes, write_volatile}; use std::result; +use std::slice; use std::usize; +use libc::iovec; + use crate::DataInit; #[derive(Eq, PartialEq, Debug)] pub enum VolatileMemoryError { /// `addr` is out of bounds of the volatile memory slice. - OutOfBounds { addr: u64 }, - /// Taking a slice at `base` with `offset` would overflow `u64`. - Overflow { base: u64, offset: u64 }, + OutOfBounds { addr: usize }, + /// Taking a slice at `base` with `offset` would overflow `usize`. + Overflow { base: usize, offset: usize }, } impl Display for VolatileMemoryError { @@ -65,7 +69,7 @@ type Result<T> = VolatileMemoryResult<T>; /// /// ``` /// # use data_model::*; -/// # fn get_slice(offset: u64, count: u64) -> VolatileMemoryResult<()> { +/// # fn get_slice(offset: usize, count: usize) -> VolatileMemoryResult<()> { /// let mem_end = calc_offset(offset, count)?; /// if mem_end > 100 { /// return Err(VolatileMemoryError::OutOfBounds{addr: mem_end}); @@ -73,7 +77,7 @@ type Result<T> = VolatileMemoryResult<T>; /// # Ok(()) /// # } /// ``` -pub fn calc_offset(base: u64, offset: u64) -> Result<u64> { +pub fn calc_offset(base: usize, offset: usize) -> Result<usize> { match base.checked_add(offset) { None => Err(Error::Overflow { base, offset }), Some(m) => Ok(m), @@ -84,109 +88,149 @@ pub fn calc_offset(base: u64, offset: u64) -> Result<u64> { pub trait VolatileMemory { /// Gets a slice of memory at `offset` that is `count` bytes in length and supports volatile /// access. - fn get_slice(&self, offset: u64, count: u64) -> Result<VolatileSlice>; + fn get_slice(&self, offset: usize, count: usize) -> Result<VolatileSlice>; /// Gets a `VolatileRef` at `offset`. - fn get_ref<T: DataInit>(&self, offset: u64) -> Result<VolatileRef<T>> { - let slice = self.get_slice(offset, size_of::<T>() as u64)?; + fn get_ref<T: DataInit>(&self, offset: usize) -> Result<VolatileRef<T>> { + let slice = self.get_slice(offset, size_of::<T>())?; Ok(VolatileRef { - addr: slice.addr as *mut T, + addr: slice.as_mut_ptr() as *mut T, phantom: PhantomData, }) } } -impl<'a> VolatileMemory for &'a mut [u8] { - fn get_slice(&self, offset: u64, count: u64) -> Result<VolatileSlice> { - let mem_end = calc_offset(offset, count)?; - if mem_end > self.len() as u64 { - return Err(Error::OutOfBounds { addr: mem_end }); - } - Ok(unsafe { VolatileSlice::new((self.as_ptr() as u64 + offset) as *mut _, count) }) - } -} - -/// A slice of raw memory that supports volatile access. -#[derive(Copy, Clone, Debug)] +/// A slice of raw memory that supports volatile access. Like `std::io::IoSliceMut`, this type is +/// guaranteed to be ABI-compatible with `libc::iovec` but unlike `IoSliceMut`, it doesn't +/// automatically deref to `&mut [u8]`. +#[derive(Copy, Clone)] +#[repr(transparent)] pub struct VolatileSlice<'a> { - addr: *mut u8, - size: u64, - phantom: PhantomData<&'a u8>, + iov: iovec, + phantom: PhantomData<&'a mut [u8]>, } impl<'a> Default for VolatileSlice<'a> { fn default() -> VolatileSlice<'a> { VolatileSlice { - addr: null_mut(), - size: 0, + iov: iovec { + iov_base: null_mut(), + iov_len: 0, + }, phantom: PhantomData, } } } +struct DebugIovec(iovec); +impl Debug for DebugIovec { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("iovec") + .field("iov_base", &self.0.iov_base) + .field("iov_len", &self.0.iov_len) + .finish() + } +} + +impl<'a> Debug for VolatileSlice<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("VolatileSlice") + .field("iov", &DebugIovec(self.iov)) + .field("phantom", &self.phantom) + .finish() + } +} + impl<'a> VolatileSlice<'a> { /// Creates a slice of raw memory that must support volatile access. + pub fn new(buf: &mut [u8]) -> VolatileSlice { + VolatileSlice { + iov: iovec { + iov_base: buf.as_mut_ptr() as *mut c_void, + iov_len: buf.len(), + }, + phantom: PhantomData, + } + } + + /// Creates a `VolatileSlice` from a pointer and a length. /// - /// To use this safely, the caller must guarantee that the memory at `addr` is `size` bytes long - /// and is available for the duration of the lifetime of the new `VolatileSlice`. The caller - /// must also guarantee that all other users of the given chunk of memory are using volatile - /// accesses. - pub unsafe fn new(addr: *mut u8, size: u64) -> VolatileSlice<'a> { + /// # Safety + /// + /// In order to use this method safely, `addr` must be valid for reads and writes of `len` bytes + /// and should live for the entire duration of lifetime `'a`. + pub unsafe fn from_raw_parts(addr: *mut u8, len: usize) -> VolatileSlice<'a> { VolatileSlice { - addr, - size, + iov: iovec { + iov_base: addr as *mut c_void, + iov_len: len, + }, phantom: PhantomData, } } - /// Gets the address of this slice's memory. - pub fn as_ptr(&self) -> *mut u8 { - self.addr + /// Gets a const pointer to this slice's memory. + pub fn as_ptr(&self) -> *const u8 { + self.iov.iov_base as *const u8 + } + + /// Gets a mutable pointer to this slice's memory. + pub fn as_mut_ptr(&self) -> *mut u8 { + self.iov.iov_base as *mut u8 } /// Gets the size of this slice. - pub fn size(&self) -> u64 { - self.size + pub fn size(&self) -> usize { + self.iov.iov_len + } + + /// Returns this `VolatileSlice` as an iovec. + pub fn as_iovec(&self) -> iovec { + self.iov + } + + /// Converts a slice of `VolatileSlice`s into a slice of `iovec`s. + pub fn as_iovecs<'slice>(iovs: &'slice [VolatileSlice<'_>]) -> &'slice [iovec] { + // Safe because `VolatileSlice` is ABI-compatible with `iovec`. + unsafe { slice::from_raw_parts(iovs.as_ptr() as *const iovec, iovs.len()) } } /// Creates a copy of this slice with the address increased by `count` bytes, and the size /// reduced by `count` bytes. - pub fn offset(self, count: u64) -> Result<VolatileSlice<'a>> { - let new_addr = - (self.addr as u64) - .checked_add(count) - .ok_or(VolatileMemoryError::Overflow { - base: self.addr as u64, - offset: count, - })?; - if new_addr > usize::MAX as u64 { - return Err(VolatileMemoryError::Overflow { - base: self.addr as u64, + pub fn offset(self, count: usize) -> Result<VolatileSlice<'a>> { + let new_addr = (self.as_mut_ptr() as usize).checked_add(count).ok_or( + VolatileMemoryError::Overflow { + base: self.as_mut_ptr() as usize, offset: count, - }); - } + }, + )?; let new_size = self - .size + .size() .checked_sub(count) .ok_or(VolatileMemoryError::OutOfBounds { addr: new_addr })?; + // Safe because the memory has the same lifetime and points to a subset of the memory of the // original slice. - unsafe { Ok(VolatileSlice::new(new_addr as *mut u8, new_size)) } + unsafe { Ok(VolatileSlice::from_raw_parts(new_addr as *mut u8, new_size)) } } /// Similar to `get_slice` but the returned slice outlives this slice. /// /// The returned slice's lifetime is still limited by the underlying data's lifetime. - pub fn sub_slice(self, offset: u64, count: u64) -> Result<VolatileSlice<'a>> { + pub fn sub_slice(self, offset: usize, count: usize) -> Result<VolatileSlice<'a>> { let mem_end = calc_offset(offset, count)?; - if mem_end > self.size { + if mem_end > self.size() { return Err(Error::OutOfBounds { addr: mem_end }); } - Ok(VolatileSlice { - addr: (self.addr as u64 + offset) as *mut _, - size: count, - phantom: PhantomData, - }) + let new_addr = (self.as_mut_ptr() as usize).checked_add(offset).ok_or( + VolatileMemoryError::Overflow { + base: self.as_mut_ptr() as usize, + offset, + }, + )?; + + // Safe because we have verified that the new memory is a subset of the original slice. + Ok(unsafe { VolatileSlice::from_raw_parts(new_addr as *mut u8, count) }) } /// Sets each byte of this slice with the given byte, similar to `memset`. @@ -196,13 +240,12 @@ impl<'a> VolatileSlice<'a> { /// # Examples /// /// ``` - /// # use data_model::VolatileMemory; + /// # use data_model::VolatileSlice; /// # fn test_write_45() -> Result<(), ()> { /// let mut mem = [0u8; 32]; - /// let mem_ref = &mut mem[..]; - /// let vslice = mem_ref.get_slice(0, 32).map_err(|_| ())?; + /// let vslice = VolatileSlice::new(&mut mem[..]); /// vslice.write_bytes(45); - /// for &mut v in mem_ref { + /// for &v in &mem[..] { /// assert_eq!(v, 45); /// } /// # Ok(()) @@ -210,7 +253,7 @@ impl<'a> VolatileSlice<'a> { pub fn write_bytes(&self, value: u8) { // Safe because the memory is valid and needs only byte alignment. unsafe { - write_bytes(self.as_ptr(), value, self.size as usize); + write_bytes(self.as_mut_ptr(), value, self.size()); } } @@ -224,11 +267,10 @@ impl<'a> VolatileSlice<'a> { /// ``` /// # use std::fs::File; /// # use std::path::Path; - /// # use data_model::VolatileMemory; + /// # use data_model::VolatileSlice; /// # fn test_write_null() -> Result<(), ()> { /// let mut mem = [0u8; 32]; - /// let mem_ref = &mut mem[..]; - /// let vslice = mem_ref.get_slice(0, 32).map_err(|_| ())?; + /// let vslice = VolatileSlice::new(&mut mem[..]); /// let mut buf = [5u8; 16]; /// vslice.copy_to(&mut buf[..]); /// for v in &buf[..] { @@ -241,8 +283,8 @@ impl<'a> VolatileSlice<'a> { where T: DataInit, { - let mut addr = self.addr; - for v in buf.iter_mut().take(self.size as usize / size_of::<T>()) { + let mut addr = self.as_mut_ptr() as *const u8; + for v in buf.iter_mut().take(self.size() / size_of::<T>()) { unsafe { *v = read_volatile(addr as *const T); addr = addr.add(size_of::<T>()); @@ -256,18 +298,21 @@ impl<'a> VolatileSlice<'a> { /// # Examples /// /// ``` - /// # use data_model::VolatileMemory; + /// # use data_model::{VolatileMemory, VolatileSlice}; /// # fn test_write_null() -> Result<(), ()> { /// let mut mem = [0u8; 32]; - /// let mem_ref = &mut mem[..]; - /// let vslice = mem_ref.get_slice(0, 32).map_err(|_| ())?; + /// let vslice = VolatileSlice::new(&mut mem[..]); /// vslice.copy_to_volatile_slice(vslice.get_slice(16, 16).map_err(|_| ())?); /// # Ok(()) /// # } /// ``` pub fn copy_to_volatile_slice(&self, slice: VolatileSlice) { unsafe { - copy(self.addr, slice.addr, min(self.size, slice.size) as usize); + copy( + self.as_mut_ptr() as *const u8, + slice.as_mut_ptr(), + min(self.size(), slice.size()), + ); } } @@ -281,11 +326,10 @@ impl<'a> VolatileSlice<'a> { /// ``` /// # use std::fs::File; /// # use std::path::Path; - /// # use data_model::VolatileMemory; + /// # use data_model::{VolatileMemory, VolatileSlice}; /// # fn test_write_null() -> Result<(), ()> { /// let mut mem = [0u8; 32]; - /// let mem_ref = &mut mem[..]; - /// let vslice = mem_ref.get_slice(0, 32).map_err(|_| ())?; + /// let vslice = VolatileSlice::new(&mut mem[..]); /// let buf = [5u8; 64]; /// vslice.copy_from(&buf[..]); /// for i in 0..4 { @@ -298,8 +342,8 @@ impl<'a> VolatileSlice<'a> { where T: DataInit, { - let mut addr = self.addr; - for &v in buf.iter().take(self.size as usize / size_of::<T>()) { + let mut addr = self.as_mut_ptr(); + for &v in buf.iter().take(self.size() / size_of::<T>()) { unsafe { write_volatile(addr as *mut T, v); addr = addr.add(size_of::<T>()); @@ -309,16 +353,8 @@ impl<'a> VolatileSlice<'a> { } impl<'a> VolatileMemory for VolatileSlice<'a> { - fn get_slice(&self, offset: u64, count: u64) -> Result<VolatileSlice> { - let mem_end = calc_offset(offset, count)?; - if mem_end > self.size { - return Err(Error::OutOfBounds { addr: mem_end }); - } - Ok(VolatileSlice { - addr: (self.addr as u64 + offset) as *mut _, - size: count, - phantom: PhantomData, - }) + fn get_slice(&self, offset: usize, count: usize) -> Result<VolatileSlice> { + self.sub_slice(offset, count) } } @@ -358,7 +394,7 @@ impl<'a, T: DataInit> VolatileRef<'a, T> { } /// Gets the address of this slice's memory. - pub fn as_ptr(&self) -> *mut T { + pub fn as_mut_ptr(&self) -> *mut T { self.addr } @@ -370,10 +406,10 @@ impl<'a, T: DataInit> VolatileRef<'a, T> { /// # use std::mem::size_of; /// # use data_model::VolatileRef; /// let v_ref = unsafe { VolatileRef::new(0 as *mut u32) }; - /// assert_eq!(v_ref.size(), size_of::<u32>() as u64); + /// assert_eq!(v_ref.size(), size_of::<u32>()); /// ``` - pub fn size(&self) -> u64 { - size_of::<T>() as u64 + pub fn size(&self) -> usize { + size_of::<T>() } /// Does a volatile write of the value `v` to the address of this ref. @@ -393,7 +429,7 @@ impl<'a, T: DataInit> VolatileRef<'a, T> { /// Converts this `T` reference to a raw slice with the same size and address. pub fn to_slice(&self) -> VolatileSlice<'a> { - unsafe { VolatileSlice::new(self.addr as *mut u8, size_of::<T>() as u64) } + unsafe { VolatileSlice::from_raw_parts(self.as_mut_ptr() as *mut u8, self.size()) } } } @@ -418,19 +454,27 @@ mod tests { } impl VolatileMemory for VecMem { - fn get_slice(&self, offset: u64, count: u64) -> Result<VolatileSlice> { + fn get_slice(&self, offset: usize, count: usize) -> Result<VolatileSlice> { let mem_end = calc_offset(offset, count)?; - if mem_end > self.mem.len() as u64 { + if mem_end > self.mem.len() { return Err(Error::OutOfBounds { addr: mem_end }); } - Ok(unsafe { VolatileSlice::new((self.mem.as_ptr() as u64 + offset) as *mut _, count) }) + + let new_addr = (self.mem.as_ptr() as usize).checked_add(offset).ok_or( + VolatileMemoryError::Overflow { + base: self.mem.as_ptr() as usize, + offset, + }, + )?; + + Ok(unsafe { VolatileSlice::from_raw_parts(new_addr as *mut u8, count) }) } } #[test] fn ref_store() { let mut a = [0u8; 1]; - let a_ref = &mut a[..]; + let a_ref = VolatileSlice::new(&mut a[..]); let v_ref = a_ref.get_ref(0).unwrap(); v_ref.store(2u8); assert_eq!(a[0], 2); @@ -440,7 +484,7 @@ mod tests { fn ref_load() { let mut a = [5u8; 1]; { - let a_ref = &mut a[..]; + let a_ref = VolatileSlice::new(&mut a[..]); let c = { let v_ref = a_ref.get_ref::<u8>(0).unwrap(); assert_eq!(v_ref.load(), 5u8); @@ -457,11 +501,11 @@ mod tests { #[test] fn ref_to_slice() { let mut a = [1u8; 5]; - let a_ref = &mut a[..]; + let a_ref = VolatileSlice::new(&mut a[..]); let v_ref = a_ref.get_ref(1).unwrap(); v_ref.store(0x12345678u32); let ref_slice = v_ref.to_slice(); - assert_eq!(v_ref.as_ptr() as u64, ref_slice.as_ptr() as u64); + assert_eq!(v_ref.as_mut_ptr() as usize, ref_slice.as_mut_ptr() as usize); assert_eq!(v_ref.size(), ref_slice.size()); } @@ -506,7 +550,7 @@ mod tests { #[test] fn slice_overflow_error() { - use std::u64::MAX; + use std::usize::MAX; let a = VecMem::new(1); let res = a.get_slice(MAX, 1).unwrap_err(); assert_eq!( @@ -528,7 +572,7 @@ mod tests { #[test] fn ref_overflow_error() { - use std::u64::MAX; + use std::usize::MAX; let a = VecMem::new(1); let res = a.get_ref::<u8>(MAX).unwrap_err(); assert_eq!( diff --git a/devices/Cargo.toml b/devices/Cargo.toml index 931ea36..8bea78d 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -7,6 +7,8 @@ edition = "2018" [features] gpu = ["gpu_buffer", "gpu_display", "gpu_renderer"] tpm = ["protos/trunks", "tpm2"] +video-decoder = ["libvda"] +video-encoder = ["libvda"] wl-dmabuf = [] x = ["gpu_display/x"] gfxstream = ["gpu"] @@ -28,6 +30,7 @@ kvm = { path = "../kvm" } kvm_sys = { path = "../kvm_sys" } libc = "*" libcras = "*" +libvda = { version = "*", optional = true } linux_input_sys = { path = "../linux_input_sys" } msg_on_socket_derive = { path = "../msg_socket/msg_on_socket_derive" } msg_socket = { path = "../msg_socket" } diff --git a/devices/src/pci/ac97_bus_master.rs b/devices/src/pci/ac97_bus_master.rs index 22f3c92..1334125 100644 --- a/devices/src/pci/ac97_bus_master.rs +++ b/devices/src/pci/ac97_bus_master.rs @@ -5,7 +5,6 @@ use std::collections::VecDeque; use std::convert::AsRef; use std::convert::TryInto; -use std::error::Error; use std::fmt::{self, Display}; use std::os::unix::io::{AsRawFd, RawFd}; use std::sync::atomic::{AtomicBool, Ordering}; @@ -15,7 +14,7 @@ use std::time::{Duration, Instant}; use audio_streams::{ shm_streams::{ShmStream, ShmStreamSource}, - DummyStreamControl, SampleFormat, StreamControl, StreamDirection, StreamEffect, + BoxError, DummyStreamControl, SampleFormat, StreamControl, StreamDirection, StreamEffect, }; use sync::{Condvar, Mutex}; use sys_util::{ @@ -105,7 +104,7 @@ type GuestMemoryResult<T> = std::result::Result<T, GuestMemoryError>; #[derive(Debug)] enum AudioError { // Failed to create a new stream. - CreateStream(Box<dyn Error>), + CreateStream(BoxError), // Invalid buffer offset received from the audio server. InvalidBufferOffset, // Guest did not provide a buffer when needed. @@ -113,9 +112,9 @@ enum AudioError { // Failure to read guest memory. ReadingGuestError(GuestMemoryError), // Failure to respond to the ServerRequest. - RespondRequest(Box<dyn Error>), + RespondRequest(BoxError), // Failure to wait for a request from the stream. - WaitForAction(Box<dyn Error>), + WaitForAction(BoxError), } impl std::error::Error for AudioError {} diff --git a/devices/src/usb/xhci/ring_buffer.rs b/devices/src/usb/xhci/ring_buffer.rs index 3033b0e..91806c6 100644 --- a/devices/src/usb/xhci/ring_buffer.rs +++ b/devices/src/usb/xhci/ring_buffer.rs @@ -264,4 +264,89 @@ mod test { let descriptor = transfer_ring.dequeue_transfer_descriptor().unwrap(); assert_eq!(descriptor.is_none(), true); } + + #[test] + fn ring_test_toggle_cycle() { + let trb_size = size_of::<Trb>() as u64; + let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x1000)]).unwrap(); + let mut transfer_ring = RingBuffer::new(String::new(), gm.clone()); + + let mut trb = NormalTrb::new(); + trb.set_trb_type(TrbType::Normal); + trb.set_data_buffer(1); + trb.set_chain(false); + trb.set_cycle(false); + gm.write_obj_at_addr(trb.clone(), GuestAddress(0x100)) + .unwrap(); + + let mut ltrb = LinkTrb::new(); + ltrb.set_trb_type(TrbType::Link); + ltrb.set_ring_segment_pointer(0x100); + ltrb.set_toggle_cycle(true); + ltrb.set_cycle(false); + gm.write_obj_at_addr(ltrb, GuestAddress(0x100 + trb_size)) + .unwrap(); + + // Initial state: consumer cycle = false + transfer_ring.set_dequeue_pointer(GuestAddress(0x100)); + transfer_ring.set_consumer_cycle_state(false); + + // Read first transfer descriptor. + let descriptor = transfer_ring + .dequeue_transfer_descriptor() + .unwrap() + .unwrap(); + assert_eq!(descriptor.len(), 1); + assert_eq!(descriptor[0].trb.get_parameter(), 1); + + // Cycle bit should be unchanged since we haven't advanced past the Link TRB yet. + assert_eq!(transfer_ring.consumer_cycle_state, false); + + // Overwrite the first TRB with a new one (data = 2) + // with the new producer cycle bit state (true). + let mut trb = NormalTrb::new(); + trb.set_trb_type(TrbType::Normal); + trb.set_data_buffer(2); + trb.set_cycle(true); // Link TRB toggled the cycle. + gm.write_obj_at_addr(trb.clone(), GuestAddress(0x100)) + .unwrap(); + + // Read new transfer descriptor. + let descriptor = transfer_ring + .dequeue_transfer_descriptor() + .unwrap() + .unwrap(); + assert_eq!(descriptor.len(), 1); + assert_eq!(descriptor[0].trb.get_parameter(), 2); + + assert_eq!(transfer_ring.consumer_cycle_state, true); + + // Update the Link TRB with the new cycle bit. + let mut ltrb = LinkTrb::new(); + ltrb.set_trb_type(TrbType::Link); + ltrb.set_ring_segment_pointer(0x100); + ltrb.set_toggle_cycle(true); + ltrb.set_cycle(true); // Producer cycle state is now 1. + gm.write_obj_at_addr(ltrb, GuestAddress(0x100 + trb_size)) + .unwrap(); + + // Overwrite the first TRB again with a new one (data = 3) + // with the new producer cycle bit state (false). + let mut trb = NormalTrb::new(); + trb.set_trb_type(TrbType::Normal); + trb.set_data_buffer(3); + trb.set_cycle(false); // Link TRB toggled the cycle. + gm.write_obj_at_addr(trb.clone(), GuestAddress(0x100)) + .unwrap(); + + // Read new transfer descriptor. + let descriptor = transfer_ring + .dequeue_transfer_descriptor() + .unwrap() + .unwrap(); + assert_eq!(descriptor.len(), 1); + assert_eq!(descriptor[0].trb.get_parameter(), 3); + + assert_eq!(transfer_ring.consumer_cycle_state, false); + } } diff --git a/devices/src/virtio/descriptor_utils.rs b/devices/src/virtio/descriptor_utils.rs index d65341b..902e3c3 100644 --- a/devices/src/virtio/descriptor_utils.rs +++ b/devices/src/virtio/descriptor_utils.rs @@ -2,9 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +use std::borrow::Cow; use std::cmp; use std::convert::TryInto; -use std::ffi::c_void; use std::fmt::{self, Display}; use std::io::{self, Read, Write}; use std::iter::FromIterator; @@ -13,10 +13,8 @@ use std::mem::{size_of, MaybeUninit}; use std::ptr::copy_nonoverlapping; use std::result; -use data_model::{DataInit, Le16, Le32, Le64, VolatileMemory, VolatileMemoryError, VolatileSlice}; -use sys_util::{ - FileReadWriteAtVolatile, FileReadWriteVolatile, GuestAddress, GuestMemory, IntoIovec, -}; +use data_model::{DataInit, Le16, Le32, Le64, VolatileMemoryError, VolatileSlice}; +use sys_util::{FileReadWriteAtVolatile, FileReadWriteVolatile, GuestAddress, GuestMemory}; use super::DescriptorChain; @@ -54,10 +52,9 @@ impl std::error::Error for Error {} #[derive(Clone)] struct DescriptorChainConsumer<'a> { - buffers: Vec<libc::iovec>, + buffers: Vec<VolatileSlice<'a>>, current: usize, bytes_consumed: usize, - mem: PhantomData<&'a GuestMemory>, } impl<'a> DescriptorChainConsumer<'a> { @@ -67,7 +64,7 @@ impl<'a> DescriptorChainConsumer<'a> { // `Reader::new()` and `Writer::new()`). self.get_remaining() .iter() - .fold(0usize, |count, buf| count + buf.iov_len) + .fold(0usize, |count, buf| count + buf.size()) } fn bytes_consumed(&self) -> usize { @@ -78,10 +75,38 @@ impl<'a> DescriptorChainConsumer<'a> { /// consume any bytes from the `DescriptorChain`. Instead callers should use the `consume` /// method to advance the `DescriptorChain`. Multiple calls to `get` with no intervening calls /// to `consume` will return the same data. - fn get_remaining(&self) -> &[libc::iovec] { + fn get_remaining(&self) -> &[VolatileSlice] { &self.buffers[self.current..] } + /// Like `get_remaining` but guarantees that the combined length of all the returned iovecs is + /// not greater than `count`. The combined length of the returned iovecs may be less than + /// `count` but will always be greater than 0 as long as there is still space left in the + /// `DescriptorChain`. + fn get_remaining_with_count(&self, count: usize) -> Cow<[VolatileSlice]> { + let iovs = self.get_remaining(); + let mut iov_count = 0; + let mut rem = count; + for iov in iovs { + if rem < iov.size() { + break; + } + + iov_count += 1; + rem -= iov.size(); + } + + // Special case where the number of bytes to be copied is smaller than the `size()` of the + // first iovec. + if iov_count == 0 && iovs.len() > 0 && count > 0 { + debug_assert!(count < iovs[0].size()); + // Safe because we know that count is smaller than the length of the first slice. + Cow::Owned(vec![iovs[0].sub_slice(0, count).unwrap()]) + } else { + Cow::Borrowed(&iovs[..iov_count]) + } + } + /// Consumes `count` bytes from the `DescriptorChain`. If `count` is larger than /// `self.available_bytes()` then all remaining bytes in the `DescriptorChain` will be consumed. /// @@ -99,19 +124,18 @@ impl<'a> DescriptorChainConsumer<'a> { break; } - let consumed = if count < buf.iov_len { + let consumed = if count < buf.size() { // Safe because we know that the iovec pointed to valid memory and we are adding a // value that is smaller than the length of the memory. - buf.iov_base = unsafe { (buf.iov_base as *mut u8).add(count) as *mut c_void }; - buf.iov_len -= count; + *buf = buf.offset(count).unwrap(); count } else { self.current += 1; - buf.iov_len + buf.size() }; - // This shouldn't overflow because `consumed <= buf.iov_len` and we already verified - // that adding all `buf.iov_len` values will not overflow when the Reader/Writer was + // This shouldn't overflow because `consumed <= buf.size()` and we already verified + // that adding all `buf.size()` values will not overflow when the Reader/Writer was // constructed. self.bytes_consumed += consumed; count -= consumed; @@ -126,81 +150,20 @@ impl<'a> DescriptorChainConsumer<'a> { let mut rem = offset; let mut end = self.current; for buf in &mut self.buffers[self.current..] { - if rem < buf.iov_len { - buf.iov_len = rem; + if rem < buf.size() { + // Safe because we are creating a smaller sub-slice. + *buf = buf.sub_slice(0, rem).unwrap(); break; } end += 1; - rem -= buf.iov_len; + rem -= buf.size(); } self.buffers.truncate(end + 1); other } - - // Temporary method for converting iovecs into VolatileSlices until we can change the - // ReadWriteVolatile traits. The irony here is that the standard implementation of the - // ReadWriteVolatile traits will convert the VolatileSlices back into iovecs. - fn get_volatile_slices(&mut self, mut count: usize) -> Vec<VolatileSlice> { - let bufs = self.get_remaining(); - let mut iovs = Vec::with_capacity(bufs.len()); - for b in bufs { - // Safe because we verified during construction that the memory at `b.iov_base` is - // `b.iov_len` bytes long. The lifetime of the `VolatileSlice` is tied to the lifetime - // of this `DescriptorChainConsumer`, which is in turn tied to the lifetime of the - // `GuestMemory` used to create it and so the memory will be available for the duration - // of the `VolatileSlice`. - let iov = unsafe { - if count < b.iov_len { - VolatileSlice::new( - b.iov_base as *mut u8, - count.try_into().expect("usize doesn't fit in u64"), - ) - } else { - VolatileSlice::new( - b.iov_base as *mut u8, - b.iov_len.try_into().expect("usize doesn't fit in u64"), - ) - } - }; - - count -= iov.size() as usize; - iovs.push(iov); - } - - iovs - } - - fn get_iovec(&mut self, len: usize) -> io::Result<DescriptorIovec<'a>> { - let mut iovec = Vec::with_capacity(self.get_remaining().len()); - - let mut rem = len; - for buf in self.get_remaining() { - let iov = if rem < buf.iov_len { - libc::iovec { - iov_base: buf.iov_base, - iov_len: rem, - } - } else { - buf.clone() - }; - - rem -= iov.iov_len; - iovec.push(iov); - - if rem == 0 { - break; - } - } - self.consume(len); - - Ok(DescriptorIovec { - iovec, - mem: PhantomData, - }) - } } /// Provides high-level interface over the sequence of memory regions @@ -249,21 +212,18 @@ impl<'a> Reader<'a> { .checked_add(desc.len as usize) .ok_or(Error::DescriptorChainOverflow)?; - let vs = mem - .get_slice(desc.addr.offset(), desc.len.into()) - .map_err(Error::VolatileMemoryError)?; - Ok(libc::iovec { - iov_base: vs.as_ptr() as *mut c_void, - iov_len: vs.size() as usize, - }) + mem.get_slice_at_addr( + desc.addr, + desc.len.try_into().expect("u32 doesn't fit in usize"), + ) + .map_err(Error::GuestMemoryError) }) - .collect::<Result<Vec<libc::iovec>>>()?; + .collect::<Result<Vec<VolatileSlice>>>()?; Ok(Reader { buffer: DescriptorChainConsumer { buffers, current: 0, bytes_consumed: 0, - mem: PhantomData, }, }) } @@ -311,7 +271,7 @@ impl<'a> Reader<'a> { mut dst: F, count: usize, ) -> io::Result<usize> { - let iovs = self.buffer.get_volatile_slices(count); + let iovs = self.buffer.get_remaining_with_count(count); let written = dst.write_vectored_volatile(&iovs[..])?; self.buffer.consume(written); Ok(written) @@ -327,7 +287,7 @@ impl<'a> Reader<'a> { count: usize, off: u64, ) -> io::Result<usize> { - let iovs = self.buffer.get_volatile_slices(count); + let iovs = self.buffer.get_remaining_with_count(count); let written = dst.write_vectored_at_volatile(&iovs[..], off)?; self.buffer.consume(written); Ok(written) @@ -392,6 +352,19 @@ impl<'a> Reader<'a> { self.buffer.bytes_consumed() } + /// Returns a `&[VolatileSlice]` that represents all the remaining data in this `Reader`. + /// Calling this method does not actually consume any data from the `Reader` and callers should + /// call `consume` to advance the `Reader`. + pub fn get_remaining(&self) -> &[VolatileSlice] { + self.buffer.get_remaining() + } + + /// Consumes `amt` bytes from the underlying descriptor chain. If `amt` is larger than the + /// remaining data left in this `Reader`, then all remaining data will be consumed. + pub fn consume(&mut self, amt: usize) { + self.buffer.consume(amt) + } + /// Splits this `Reader` into two at the given offset in the `DescriptorChain` buffer. After the /// split, `self` will be able to read up to `offset` bytes while the returned `Reader` can read /// up to `available_bytes() - offset` bytes. If `offset > self.available_bytes()`, then the @@ -401,12 +374,6 @@ impl<'a> Reader<'a> { buffer: self.buffer.split_at(offset), } } - - /// Returns a DescriptorIovec for the next `len` bytes of the descriptor chain - /// buffer, which can be used as an IntoIovec. - pub fn get_iovec(&mut self, len: usize) -> io::Result<DescriptorIovec<'a>> { - self.buffer.get_iovec(len) - } } impl<'a> io::Read for Reader<'a> { @@ -418,11 +385,11 @@ impl<'a> io::Read for Reader<'a> { break; } - let count = cmp::min(rem.len(), b.iov_len); + let count = cmp::min(rem.len(), b.size()); // Safe because we have already verified that `b` points to valid memory. unsafe { - copy_nonoverlapping(b.iov_base as *const u8, rem.as_mut_ptr(), count); + copy_nonoverlapping(b.as_ptr(), rem.as_mut_ptr(), count); } rem = &mut rem[count..]; total += count; @@ -460,21 +427,18 @@ impl<'a> Writer<'a> { .checked_add(desc.len as usize) .ok_or(Error::DescriptorChainOverflow)?; - let vs = mem - .get_slice(desc.addr.offset(), desc.len.into()) - .map_err(Error::VolatileMemoryError)?; - Ok(libc::iovec { - iov_base: vs.as_ptr() as *mut c_void, - iov_len: vs.size() as usize, - }) + mem.get_slice_at_addr( + desc.addr, + desc.len.try_into().expect("u32 doesn't fit in usize"), + ) + .map_err(Error::GuestMemoryError) }) - .collect::<Result<Vec<libc::iovec>>>()?; + .collect::<Result<Vec<VolatileSlice>>>()?; Ok(Writer { buffer: DescriptorChainConsumer { buffers, current: 0, bytes_consumed: 0, - mem: PhantomData, }, }) } @@ -512,7 +476,7 @@ impl<'a> Writer<'a> { mut src: F, count: usize, ) -> io::Result<usize> { - let iovs = self.buffer.get_volatile_slices(count); + let iovs = self.buffer.get_remaining_with_count(count); let read = src.read_vectored_volatile(&iovs[..])?; self.buffer.consume(read); Ok(read) @@ -528,7 +492,7 @@ impl<'a> Writer<'a> { count: usize, off: u64, ) -> io::Result<usize> { - let iovs = self.buffer.get_volatile_slices(count); + let iovs = self.buffer.get_remaining_with_count(count); let read = src.read_vectored_at_volatile(&iovs[..], off)?; self.buffer.consume(read); Ok(read) @@ -595,12 +559,6 @@ impl<'a> Writer<'a> { buffer: self.buffer.split_at(offset), } } - - /// Returns a DescriptorIovec for the next `len` bytes of the descriptor chain - /// buffer, which can be used as an IntoIovec. - pub fn get_iovec(&mut self, len: usize) -> io::Result<DescriptorIovec<'a>> { - self.buffer.get_iovec(len) - } } impl<'a> io::Write for Writer<'a> { @@ -612,10 +570,10 @@ impl<'a> io::Write for Writer<'a> { break; } - let count = cmp::min(rem.len(), b.iov_len); + let count = cmp::min(rem.len(), b.size()); // Safe because we have already verified that `vs` points to valid memory. unsafe { - copy_nonoverlapping(rem.as_ptr(), b.iov_base as *mut u8, count); + copy_nonoverlapping(rem.as_ptr(), b.as_mut_ptr(), count); } rem = &rem[count..]; total += count; @@ -631,18 +589,6 @@ impl<'a> io::Write for Writer<'a> { } } -pub struct DescriptorIovec<'a> { - iovec: Vec<libc::iovec>, - mem: PhantomData<&'a GuestMemory>, -} - -// Safe because the lifetime of DescriptorIovec is tied to the underlying GuestMemory. -unsafe impl<'a> IntoIovec for DescriptorIovec<'a> { - fn into_iovec(&self) -> Vec<libc::iovec> { - self.iovec.clone() - } -} - const VIRTQ_DESC_F_NEXT: u16 = 0x1; const VIRTQ_DESC_F_WRITE: u16 = 0x2; @@ -1266,4 +1212,59 @@ mod tests { .expect("failed to collect() values"); assert_eq!(vs, vs_read); } + + #[test] + fn get_remaining_with_count() { + use DescriptorType::*; + + let memory_start_addr = GuestAddress(0x0); + let memory = GuestMemory::new(&vec![(memory_start_addr, 0x10000)]).unwrap(); + + let chain = create_descriptor_chain( + &memory, + GuestAddress(0x0), + GuestAddress(0x100), + vec![ + (Readable, 16), + (Readable, 16), + (Readable, 96), + (Writable, 64), + (Writable, 1), + (Writable, 3), + ], + 0, + ) + .expect("create_descriptor_chain failed"); + + let Reader { mut buffer } = Reader::new(&memory, chain).expect("failed to create Reader"); + + let drain = buffer + .get_remaining_with_count(::std::usize::MAX) + .iter() + .fold(0usize, |total, iov| total + iov.size()); + assert_eq!(drain, 128); + + let exact = buffer + .get_remaining_with_count(32) + .iter() + .fold(0usize, |total, iov| total + iov.size()); + assert!(exact > 0); + assert!(exact <= 32); + + let split = buffer + .get_remaining_with_count(24) + .iter() + .fold(0usize, |total, iov| total + iov.size()); + assert!(split > 0); + assert!(split <= 24); + + buffer.consume(64); + + let first = buffer + .get_remaining_with_count(8) + .iter() + .fold(0usize, |total, iov| total + iov.size()); + assert!(first > 0); + assert!(first <= 8); + } } diff --git a/devices/src/virtio/gpu/virtio_2d_backend.rs b/devices/src/virtio/gpu/virtio_2d_backend.rs index fd85bef..9bdcc9b 100644 --- a/devices/src/virtio/gpu/virtio_2d_backend.rs +++ b/devices/src/virtio/gpu/virtio_2d_backend.rs @@ -199,7 +199,7 @@ pub fn transfer<'a, S: Iterator<Item = VolatileSlice<'a>>>( } let src_subslice = src - .get_slice(offset_within_src, copyable_size) + .get_slice(offset_within_src as usize, copyable_size as usize) .map_err(|e| Error::MemCopy(e))?; let dst_line_vertical_offset = checked_arithmetic!(current_height * dst_stride)?; @@ -210,7 +210,7 @@ pub fn transfer<'a, S: Iterator<Item = VolatileSlice<'a>>>( let dst_start_offset = checked_arithmetic!(dst_resource_offset + dst_line_offset)?; let dst_subslice = dst - .get_slice(dst_start_offset, copyable_size) + .get_slice(dst_start_offset as usize, copyable_size as usize) .map_err(|e| Error::MemCopy(e))?; src_subslice.copy_to_volatile_slice(dst_subslice); @@ -246,7 +246,7 @@ impl Virtio2DResource { ) -> bool { if iovecs .iter() - .any(|&(addr, len)| mem.get_slice(addr.offset(), len as u64).is_err()) + .any(|&(addr, len)| mem.get_slice_at_addr(addr, len).is_err()) { return false; } @@ -303,20 +303,18 @@ impl VirtioResource for Virtio2DResource { if self .guest_iovecs .iter() - .any(|&(addr, len)| guest_mem.get_slice(addr.offset(), len as u64).is_err()) + .any(|&(addr, len)| guest_mem.get_slice_at_addr(addr, len).is_err()) { error!("failed to write to resource: invalid iovec attached"); return; } - let mut src_slices = Vec::new(); - for (addr, len) in &self.guest_iovecs { + let mut src_slices = Vec::with_capacity(self.guest_iovecs.len()); + for &(addr, len) in &self.guest_iovecs { // Unwrap will not panic because we already checked the slices. - src_slices.push(guest_mem.get_slice(addr.offset(), *len as u64).unwrap()); + src_slices.push(guest_mem.get_slice_at_addr(addr, len).unwrap()); } - let host_mem_len = self.host_mem.len() as u64; - let src_stride = self.host_mem_stride; let src_offset = src_offset; @@ -332,10 +330,7 @@ impl VirtioResource for Virtio2DResource { height, dst_stride, dst_offset, - self.host_mem - .as_mut_slice() - .get_slice(0, host_mem_len) - .unwrap(), + VolatileSlice::new(self.host_mem.as_mut_slice()), src_stride, src_offset, src_slices.iter().cloned(), @@ -359,8 +354,6 @@ impl VirtioResource for Virtio2DResource { let dst_offset = 0; - let host_mem_len = self.host_mem.len() as u64; - if let Err(e) = transfer( self.width(), self.height(), @@ -373,13 +366,9 @@ impl VirtioResource for Virtio2DResource { dst, src_stride, src_offset, - [self - .host_mem - .as_mut_slice() - .get_slice(0, host_mem_len) - .unwrap()] - .iter() - .cloned(), + [VolatileSlice::new(self.host_mem.as_mut_slice())] + .iter() + .cloned(), ) { error!("failed to read from resource: {}", e); } diff --git a/devices/src/virtio/gpu/virtio_backend.rs b/devices/src/virtio/gpu/virtio_backend.rs index bb1db4a..3200fa2 100644 --- a/devices/src/virtio/gpu/virtio_backend.rs +++ b/devices/src/virtio/gpu/virtio_backend.rs @@ -124,9 +124,9 @@ impl VirtioBackend { ) -> GpuResponse { let mut response = GpuResponse::OkNoData; - if let Some(scannout_resource_id) = self.scanout_resource_id { - if scannout_resource_id.get() == resource_id { - response = self.flush_scannout_resource_to_surface(resource); + if let Some(scanout_resource_id) = self.scanout_resource_id { + if scanout_resource_id.get() == resource_id { + response = self.flush_scanout_resource_to_surface(resource); } } @@ -143,7 +143,7 @@ impl VirtioBackend { response } - pub fn flush_scannout_resource_to_surface( + pub fn flush_scanout_resource_to_surface( &mut self, resource: &mut dyn VirtioResource, ) -> GpuResponse { diff --git a/devices/src/virtio/gpu/virtio_gfxstream_backend.rs b/devices/src/virtio/gpu/virtio_gfxstream_backend.rs index d8ef793..b2a9fb7 100644 --- a/devices/src/virtio/gpu/virtio_gfxstream_backend.rs +++ b/devices/src/virtio/gpu/virtio_gfxstream_backend.rs @@ -16,7 +16,6 @@ use std::panic; use std::rc::Rc; use std::usize; -use data_model::*; use gpu_display::*; use gpu_renderer::RendererFlags; use resources::Alloc; @@ -467,7 +466,7 @@ impl Backend for VirtioGfxStreamBackend { let mut backing_iovecs: Vec<iovec> = Vec::new(); for (addr, len) in vecs { - let slice = mem.get_slice(addr.offset(), len as u64).unwrap(); + let slice = mem.get_slice_at_addr(addr, len).unwrap(); backing_iovecs.push(iovec { iov_base: slice.as_ptr() as *mut c_void, iov_len: len as usize, diff --git a/devices/src/virtio/mod.rs b/devices/src/virtio/mod.rs index 4d5d2cb..ce62551 100644 --- a/devices/src/virtio/mod.rs +++ b/devices/src/virtio/mod.rs @@ -17,6 +17,8 @@ mod queue; mod rng; #[cfg(feature = "tpm")] mod tpm; +#[cfg(any(feature = "video-decoder", feature = "video-encoder"))] +mod video; mod virtio_device; mod virtio_pci_common_config; mod virtio_pci_device; @@ -44,6 +46,8 @@ pub use self::queue::*; pub use self::rng::*; #[cfg(feature = "tpm")] pub use self::tpm::*; +#[cfg(any(feature = "video-decoder", feature = "video-encoder"))] +pub use self::video::*; pub use self::virtio_device::*; pub use self::virtio_pci_device::*; pub use self::wl::*; @@ -76,6 +80,8 @@ const TYPE_CRYPTO: u32 = 20; const TYPE_IOMMU: u32 = 23; const TYPE_FS: u32 = 26; const TYPE_PMEM: u32 = 27; +const TYPE_VIDEO_ENC: u32 = 30; +const TYPE_VIDEO_DEC: u32 = 31; // Additional types invented by crosvm const MAX_VIRTIO_DEVICE_ID: u32 = 63; const TYPE_WL: u32 = MAX_VIRTIO_DEVICE_ID; @@ -114,6 +120,8 @@ pub fn type_to_str(type_: u32) -> Option<&'static str> { TYPE_PMEM => "pmem", TYPE_WL => "wl", TYPE_TPM => "tpm", + TYPE_VIDEO_DEC => "video-decoder", + TYPE_VIDEO_ENC => "video-encoder", _ => return None, }) } diff --git a/devices/src/virtio/video/command.rs b/devices/src/virtio/video/command.rs new file mode 100644 index 0000000..7bdb335 --- /dev/null +++ b/devices/src/virtio/video/command.rs @@ -0,0 +1,335 @@ +// 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. + +//! Data structures for commands of virtio video devices. + +use std::convert::{TryFrom, TryInto}; +use std::fmt; +use std::io; + +use data_model::Le32; +use enumn::N; +use sys_util::error; + +use crate::virtio::video::control::*; +use crate::virtio::video::format::*; +use crate::virtio::video::params::Params; +use crate::virtio::video::protocol::*; +use crate::virtio::Reader; + +/// An error indicating a failure while reading a request from the guest. +#[derive(Debug)] +pub enum ReadCmdError { + /// Failure while reading an object. + IoError(io::Error), + /// Invalid arguement is passed, + InvalidArgument, + /// The type of the command was invalid. + InvalidCmdType(u32), + /// The type of the requested control was unsupported. + UnsupportedCtrlType(u32), +} + +impl fmt::Display for ReadCmdError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::ReadCmdError::*; + match self { + IoError(e) => write!(f, "failed to read an object: {}", e), + InvalidArgument => write!(f, "invalid arguement is passed in command"), + InvalidCmdType(t) => write!(f, "invalid command type: {}", t), + UnsupportedCtrlType(t) => write!(f, "unsupported control type: {}", t), + } + } +} + +impl std::error::Error for ReadCmdError {} + +impl From<io::Error> for ReadCmdError { + fn from(e: io::Error) -> ReadCmdError { + ReadCmdError::IoError(e) + } +} + +#[derive(PartialEq, Eq, PartialOrd, Ord, N, Clone, Copy, Debug)] +#[repr(u32)] +pub enum QueueType { + Input = VIRTIO_VIDEO_QUEUE_TYPE_INPUT, + Output = VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT, +} +impl_try_from_le32_for_enumn!(QueueType, "queue_type"); + +pub enum VideoCmd { + QueryCapability { + queue_type: QueueType, + }, + StreamCreate { + stream_id: u32, + coded_format: Format, + }, + StreamDestroy { + stream_id: u32, + }, + StreamDrain { + stream_id: u32, + }, + ResourceCreate { + stream_id: u32, + queue_type: QueueType, + resource_id: u32, + plane_offsets: Vec<u32>, + uuid: u128, + }, + ResourceQueue { + stream_id: u32, + queue_type: QueueType, + resource_id: u32, + timestamp: u64, + data_sizes: Vec<u32>, + }, + ResourceDestroyAll { + stream_id: u32, + }, + QueueClear { + stream_id: u32, + queue_type: QueueType, + }, + GetParams { + stream_id: u32, + queue_type: QueueType, + }, + SetParams { + stream_id: u32, + queue_type: QueueType, + params: Params, + }, + QueryControl { + query_ctrl_type: QueryCtrlType, + }, + GetControl { + stream_id: u32, + ctrl_type: CtrlType, + }, + SetControl { + stream_id: u32, + ctrl_val: CtrlVal, + }, +} + +impl<'a> VideoCmd { + /// Reads a request on virtqueue and construct a VideoCmd value. + pub fn from_reader(r: &'a mut Reader<'a>) -> Result<Self, ReadCmdError> { + use self::ReadCmdError::*; + use self::VideoCmd::*; + + // Unlike structs in virtio_video.h in the kernel, our command structs in protocol.rs don't + // have a field of `struct virtio_video_cmd_hdr`. So, we first read the header here and + // a body below. + let hdr = r.read_obj::<virtio_video_cmd_hdr>()?; + + Ok(match hdr.type_.into() { + VIRTIO_VIDEO_CMD_QUERY_CAPABILITY => { + let virtio_video_query_capability { queue_type, .. } = r.read_obj()?; + QueryCapability { + queue_type: queue_type.try_into()?, + } + } + VIRTIO_VIDEO_CMD_STREAM_CREATE => { + let virtio_video_stream_create { + in_mem_type, + out_mem_type, + coded_format, + .. + } = r.read_obj()?; + + if in_mem_type != VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT + || out_mem_type != VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT + { + error!("mem_type must be VIRTIO_OBJECT"); + return Err(InvalidArgument); + } + StreamCreate { + stream_id: hdr.stream_id.into(), + coded_format: coded_format.try_into()?, + } + } + VIRTIO_VIDEO_CMD_STREAM_DESTROY => { + let virtio_video_stream_destroy { .. } = r.read_obj()?; + StreamDestroy { + stream_id: hdr.stream_id.into(), + } + } + VIRTIO_VIDEO_CMD_STREAM_DRAIN => { + let virtio_video_stream_drain { .. } = r.read_obj()?; + StreamDrain { + stream_id: hdr.stream_id.into(), + } + } + VIRTIO_VIDEO_CMD_RESOURCE_CREATE => { + let virtio_video_resource_create { + queue_type, + resource_id, + planes_layout, + num_planes, + plane_offsets, + .. + } = r.read_obj()?; + + // Assume ChromeOS-specific requirements. + if Into::<u32>::into(planes_layout) != VIRTIO_VIDEO_PLANES_LAYOUT_SINGLE_BUFFER { + error!( + "each buffer must be a single DMAbuf: {}", + Into::<u32>::into(planes_layout), + ); + return Err(InvalidArgument); + } + + let num_planes: u32 = num_planes.into(); + if num_planes as usize > plane_offsets.len() { + error!( + "num_planes must not exceed {} but {}", + plane_offsets.len(), + num_planes + ); + return Err(InvalidArgument); + } + let plane_offsets = plane_offsets[0..num_planes as usize] + .iter() + .map(|x| Into::<u32>::into(*x)) + .collect::<Vec<u32>>(); + + let virtio_video_object_entry { uuid } = r.read_obj()?; + + ResourceCreate { + stream_id: hdr.stream_id.into(), + queue_type: queue_type.try_into()?, + resource_id: resource_id.into(), + plane_offsets, + uuid: u128::from_be_bytes(uuid), + } + } + VIRTIO_VIDEO_CMD_RESOURCE_QUEUE => { + let virtio_video_resource_queue { + queue_type, + resource_id, + timestamp, + num_data_sizes, + data_sizes, + .. + } = r.read_obj()?; + + let num_data_sizes: u32 = num_data_sizes.into(); + if num_data_sizes as usize > data_sizes.len() { + return Err(InvalidArgument); + } + let data_sizes = data_sizes[0..num_data_sizes as usize] + .iter() + .map(|x| Into::<u32>::into(*x)) + .collect::<Vec<u32>>(); + ResourceQueue { + stream_id: hdr.stream_id.into(), + queue_type: queue_type.try_into()?, + resource_id: resource_id.into(), + timestamp: timestamp.into(), + data_sizes, + } + } + VIRTIO_VIDEO_CMD_RESOURCE_DESTROY_ALL => { + let virtio_video_resource_destroy_all { + + // `queue_type` should be ignored because destroy_all will affect both queues. + // This field exists here by mistake. + .. + } = r.read_obj()?; + ResourceDestroyAll { + stream_id: hdr.stream_id.into(), + } + } + VIRTIO_VIDEO_CMD_QUEUE_CLEAR => { + let virtio_video_queue_clear { queue_type, .. } = r.read_obj()?; + QueueClear { + stream_id: hdr.stream_id.into(), + queue_type: queue_type.try_into()?, + } + } + VIRTIO_VIDEO_CMD_GET_PARAMS => { + let virtio_video_get_params { queue_type, .. } = r.read_obj()?; + GetParams { + stream_id: hdr.stream_id.into(), + queue_type: queue_type.try_into()?, + } + } + VIRTIO_VIDEO_CMD_SET_PARAMS => { + let virtio_video_set_params { params } = r.read_obj()?; + SetParams { + stream_id: hdr.stream_id.into(), + queue_type: params.queue_type.try_into()?, + params: params.try_into()?, + } + } + VIRTIO_VIDEO_CMD_QUERY_CONTROL => { + let body = r.read_obj::<virtio_video_query_control>()?; + let query_ctrl_type = match body.control.into() { + VIRTIO_VIDEO_CONTROL_BITRATE => QueryCtrlType::Bitrate, + VIRTIO_VIDEO_CONTROL_PROFILE => QueryCtrlType::Profile( + r.read_obj::<virtio_video_query_control_profile>()? + .format + .try_into()?, + ), + VIRTIO_VIDEO_CONTROL_LEVEL => QueryCtrlType::Level( + r.read_obj::<virtio_video_query_control_level>()? + .format + .try_into()?, + ), + t => { + return Err(ReadCmdError::UnsupportedCtrlType(t)); + } + }; + QueryControl { query_ctrl_type } + } + VIRTIO_VIDEO_CMD_GET_CONTROL => { + let virtio_video_get_control { control, .. } = r.read_obj()?; + let ctrl_type = match control.into() { + VIRTIO_VIDEO_CONTROL_BITRATE => CtrlType::Bitrate, + VIRTIO_VIDEO_CONTROL_PROFILE => CtrlType::Profile, + VIRTIO_VIDEO_CONTROL_LEVEL => CtrlType::Level, + t => { + return Err(ReadCmdError::UnsupportedCtrlType(t)); + } + }; + GetControl { + stream_id: hdr.stream_id.into(), + ctrl_type, + } + } + VIRTIO_VIDEO_CMD_SET_CONTROL => { + let virtio_video_set_control { control, .. } = r.read_obj()?; + let ctrl_val = match control.into() { + VIRTIO_VIDEO_CONTROL_BITRATE => CtrlVal::Bitrate( + r.read_obj::<virtio_video_control_val_bitrate>()? + .bitrate + .into(), + ), + VIRTIO_VIDEO_CONTROL_PROFILE => CtrlVal::Profile( + r.read_obj::<virtio_video_control_val_profile>()? + .profile + .try_into()?, + ), + VIRTIO_VIDEO_CONTROL_LEVEL => CtrlVal::Level( + r.read_obj::<virtio_video_control_val_level>()? + .level + .try_into()?, + ), + t => { + return Err(ReadCmdError::UnsupportedCtrlType(t)); + } + }; + SetControl { + stream_id: hdr.stream_id.into(), + ctrl_val, + } + } + _ => return Err(ReadCmdError::InvalidCmdType(hdr.type_.into())), + }) + } +} diff --git a/devices/src/virtio/video/control.rs b/devices/src/virtio/video/control.rs new file mode 100644 index 0000000..b9756c9 --- /dev/null +++ b/devices/src/virtio/video/control.rs @@ -0,0 +1,83 @@ +// 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. + +//! Implementation of data structures for virtio-video controls. + +use std::convert::From; +use std::io; + +use data_model::Le32; + +use crate::virtio::video::format::{Format, Level, Profile}; +use crate::virtio::video::protocol::*; +use crate::virtio::video::response::Response; +use crate::virtio::Writer; + +#[derive(Debug)] +pub enum QueryCtrlType { + Bitrate, + Profile(Format), + Level(Format), +} + +#[derive(Debug)] +pub enum QueryCtrlResponse { + Profile(Vec<Profile>), + #[allow(dead_code)] + Level(Vec<Level>), +} + +impl Response for QueryCtrlResponse { + fn write(&self, w: &mut Writer) -> Result<(), io::Error> { + match self { + QueryCtrlResponse::Profile(ps) => { + w.write_obj(virtio_video_query_control_resp_profile { + num: Le32::from(ps.len() as u32), + ..Default::default() + })?; + w.write_iter(ps.iter().map(|p| Le32::from(*p as u32))) + } + QueryCtrlResponse::Level(ls) => { + w.write_obj(virtio_video_query_control_resp_level { + num: Le32::from(ls.len() as u32), + ..Default::default() + })?; + w.write_iter(ls.iter().map(|l| Le32::from(*l as u32))) + } + } + } +} + +#[derive(Debug)] +pub enum CtrlType { + Bitrate, + Profile, + Level, +} + +#[derive(Debug)] +pub enum CtrlVal { + Bitrate(u32), + Profile(Profile), + Level(Level), +} + +impl Response for CtrlVal { + fn write(&self, w: &mut Writer) -> Result<(), io::Error> { + match self { + CtrlVal::Bitrate(r) => w.write_obj(virtio_video_control_val_bitrate { + bitrate: Le32::from(*r), + ..Default::default() + }), + CtrlVal::Profile(p) => w.write_obj(virtio_video_control_val_profile { + profile: Le32::from(*p as u32), + ..Default::default() + }), + CtrlVal::Level(l) => w.write_obj(virtio_video_control_val_level { + level: Le32::from(*l as u32), + ..Default::default() + }), + } + } +} diff --git a/devices/src/virtio/video/decoder/capability.rs b/devices/src/virtio/video/decoder/capability.rs new file mode 100644 index 0000000..b6d09af --- /dev/null +++ b/devices/src/virtio/video/decoder/capability.rs @@ -0,0 +1,138 @@ +// 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. + +//! Capablities of the virtio video decoder device. + +use std::collections::BTreeMap; + +use crate::virtio::video::control::*; +use crate::virtio::video::format::*; + +fn from_input_format(fmt: &libvda::InputFormat, mask: u64) -> FormatDesc { + let format = match fmt.profile { + libvda::Profile::VP8 => Format::VP8, + libvda::Profile::VP9Profile0 => Format::VP9, + libvda::Profile::H264 => Format::H264, + }; + FormatDesc { + mask, + format, + frame_formats: vec![Default::default()], + } +} + +fn from_pixel_format( + fmt: &libvda::PixelFormat, + mask: u64, + width_range: FormatRange, + height_range: FormatRange, +) -> FormatDesc { + let format = match fmt { + libvda::PixelFormat::NV12 => Format::NV12, + libvda::PixelFormat::YV12 => Format::YUV420, + }; + + let frame_formats = vec![FrameFormat { + width: width_range, + height: height_range, + bitrates: Vec::new(), + }]; + + FormatDesc { + mask, + format, + frame_formats, + } +} + +pub struct Capability { + pub in_fmts: Vec<FormatDesc>, + pub out_fmts: Vec<FormatDesc>, + + // Stores supporterd profiles and levels for each format. + profiles: BTreeMap<Format, Vec<Profile>>, + levels: BTreeMap<Format, Vec<Level>>, +} + +impl Capability { + pub fn new(caps: &libvda::Capabilities) -> Self { + // Raise the first |# of supported raw formats|-th bits because we can assume that any + // combination of (a coded format, a raw format) is valid in Chrome. + let mask = !(u64::max_value() << caps.output_formats.len()); + let in_fmts = caps + .input_formats + .iter() + .map(|fmt| from_input_format(fmt, mask)) + .collect(); + + // Prepare {min, max} of {width, height}. + // While these values are associated with each input format in libvda, + // they are associated with each output format in virtio-video protocol. + // Thus, we compute max of min values and min of max values here. + let min_width = caps.input_formats.iter().map(|fmt| fmt.min_width).max(); + let max_width = caps.input_formats.iter().map(|fmt| fmt.max_width).min(); + let min_height = caps.input_formats.iter().map(|fmt| fmt.min_height).max(); + let max_height = caps.input_formats.iter().map(|fmt| fmt.max_height).min(); + let width_range = FormatRange { + min: min_width.unwrap_or(0), + max: max_width.unwrap_or(0), + step: 1, + }; + let height_range = FormatRange { + min: min_height.unwrap_or(0), + max: max_height.unwrap_or(0), + step: 1, + }; + + // Raise the first |# of supported coded formats|-th bits because we can assume that any + // combination of (a coded format, a raw format) is valid in Chrome. + let mask = !(u64::max_value() << caps.input_formats.len()); + let out_fmts = caps + .output_formats + .iter() + .map(|fmt| from_pixel_format(fmt, mask, width_range, height_range)) + .collect(); + + let mut profiles: BTreeMap<Format, Vec<Profile>> = Default::default(); + let mut levels: BTreeMap<Format, Vec<Level>> = Default::default(); + for fmt in caps.input_formats.iter() { + match fmt.profile { + libvda::Profile::VP8 => { + profiles.insert(Format::VP8, vec![Profile::VP8Profile0]); + } + libvda::Profile::VP9Profile0 => { + profiles.insert(Format::VP9, vec![Profile::VP9Profile0]); + } + libvda::Profile::H264 => { + profiles.insert(Format::H264, vec![Profile::H264Baseline]); + levels.insert(Format::H264, vec![Level::H264_1_0]); + } + }; + } + + Capability { + in_fmts, + out_fmts, + profiles, + levels, + } + } + + pub fn query_control(&self, t: &QueryCtrlType) -> Option<QueryCtrlResponse> { + use QueryCtrlType::*; + match *t { + Profile(fmt) => { + let profiles = self.profiles.get(&fmt)?; + Some(QueryCtrlResponse::Profile( + profiles.iter().copied().collect(), + )) + } + Level(fmt) => { + let levels = self.levels.get(&fmt)?; + Some(QueryCtrlResponse::Level(levels.iter().copied().collect())) + } + _ => None, + } + } +} diff --git a/devices/src/virtio/video/decoder/mod.rs b/devices/src/virtio/video/decoder/mod.rs new file mode 100644 index 0000000..3516cad --- /dev/null +++ b/devices/src/virtio/video/decoder/mod.rs @@ -0,0 +1,976 @@ +// 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. + +//! Implementation of a virtio video decoder device backed by LibVDA. + +use std::collections::btree_map::Entry; +use std::collections::BTreeMap; +use std::convert::TryInto; +use std::fs::File; +use std::os::unix::io::{AsRawFd, IntoRawFd}; + +use sys_util::{error, PollContext}; + +use crate::virtio::resource_bridge::{self, ResourceInfo, ResourceRequestSocket}; +use crate::virtio::video::command::{QueueType, VideoCmd}; +use crate::virtio::video::control::{CtrlType, CtrlVal, QueryCtrlResponse, QueryCtrlType}; +use crate::virtio::video::device::*; +use crate::virtio::video::error::*; +use crate::virtio::video::event::*; +use crate::virtio::video::format::*; +use crate::virtio::video::params::Params; +use crate::virtio::video::response::CmdResponse; + +mod capability; +use capability::*; + +type StreamId = u32; +type ResourceId = u32; + +// ResourceId given by the driver +type InputResourceId = u32; +type OutputResourceId = u32; + +// Id for a frame buffer passed to Chrome. +// We cannot use OutputResourceId as is because this ID must be between 0 and ((# of buffers) - 1). +// +// TODO(b/1518105): Once we decide to generate resource_id in the device side, +// we don't need this value and can pass OutputResourceId to Chrome directly. +type FrameBufferId = i32; + +type ResourceHandle = u32; +type Timestamp = u64; + +// Represents queue types of pending Clear commands if exist. +#[derive(Default)] +struct PendingClearCmds { + input: bool, + output: bool, +} + +// Context is associated with one `libvda::Session`, which corresponds to one stream from the +// virtio-video's point of view. +#[derive(Default)] +struct Context { + stream_id: StreamId, + + in_params: Params, + out_params: Params, + + // Timestamp -> InputResourceId + timestamp_to_input_res_id: BTreeMap<Timestamp, InputResourceId>, + // {Input,Output}ResourceId -> ResourceHandle + res_id_to_res_handle: BTreeMap<u32, ResourceHandle>, + + // OutputResourceId <-> FrameBufferId + res_id_to_frame_buf_id: BTreeMap<OutputResourceId, FrameBufferId>, + frame_buf_id_to_res_id: BTreeMap<FrameBufferId, OutputResourceId>, + + keep_resources: Vec<File>, + + // Stores queue types of pending Clear commands if exist. + // This is needed because libvda's Reset API clears both queues while virtio-video's Clear is + // called for each queue. + pending_clear_cmds: PendingClearCmds, + + // This is a flag that shows whether libvda's set_output_buffer_count is called. + // This will be set to true when ResourceCreate for OutputBuffer is called for the first time. + // + // TODO(b/1518105): This field is added as a hack because the current virtio-video v3 spec + // doesn't have a way to send a number of frame buffers the guest provides. + // Once we have the way in the virtio-video protocol, we should remove this flag. + set_output_buffer_count: bool, + + // Reserves output resource that will be used to notify EOS. + // This resource must not be enqueued to Chrome. + keep_notification_output_buffer: Option<OutputResourceId>, +} + +impl Context { + fn new(stream_id: StreamId, format: Format) -> Self { + Context { + stream_id, + in_params: Params { + format: Some(format), + min_buffers: 2, + max_buffers: 32, + plane_formats: vec![Default::default()], + ..Default::default() + }, + out_params: Default::default(), + set_output_buffer_count: false, + ..Default::default() + } + } + + fn get_resource_info( + &self, + res_bridge: &ResourceRequestSocket, + resource_id: u32, + ) -> VideoResult<ResourceInfo> { + let handle = self.res_id_to_res_handle.get(&resource_id).copied().ok_or( + VideoError::InvalidResourceId { + stream_id: self.stream_id, + resource_id, + }, + )?; + resource_bridge::get_resource_info(res_bridge, handle) + .map_err(VideoError::ResourceBridgeFailure) + } + + fn register_buffer(&mut self, resource_id: u32, uuid: &u128) { + // TODO(stevensd): `Virtio3DBackend::resource_assign_uuid` is currently implemented to use + // 32-bits resource_handles as UUIDs. Once it starts using real UUIDs, we need to update + // this conversion. + let handle = TryInto::<u32>::try_into(*uuid).expect("uuid is larger than 32 bits"); + self.res_id_to_res_handle.insert(resource_id, handle); + } + + fn register_queued_frame_buffer(&mut self, resource_id: OutputResourceId) -> FrameBufferId { + // Generate a new FrameBufferId + let id = self.res_id_to_frame_buf_id.len() as FrameBufferId; + self.res_id_to_frame_buf_id.insert(resource_id, id); + self.frame_buf_id_to_res_id.insert(id, resource_id); + id + } + + fn reset(&mut self) { + // Reset `Context` except parameters. + *self = Context { + stream_id: self.stream_id, + in_params: self.in_params.clone(), + out_params: self.out_params.clone(), + ..Default::default() + } + } + + /* + * Functions handling libvda events. + */ + + fn handle_provide_picture_buffers( + &mut self, + min_num_buffers: u32, + width: i32, + height: i32, + visible_rect_left: i32, + visible_rect_top: i32, + visible_rect_right: i32, + visible_rect_bottom: i32, + ) { + // We only support NV12. + let format = Some(Format::NV12); + + let rect_width: u32 = (visible_rect_right - visible_rect_left) as u32; + let rect_height: u32 = (visible_rect_bottom - visible_rect_top) as u32; + + let plane_size = rect_width * rect_height; + let stride = rect_width; + let plane_formats = vec![ + PlaneFormat { plane_size, stride }, + PlaneFormat { plane_size, stride }, + ]; + + self.out_params = Params { + format, + // Note that rect_width is sometimes smaller. + frame_width: width as u32, + frame_height: height as u32, + // Adding 1 to `min_buffers` to reserve a resource for `keep_notification_output_buffer`. + min_buffers: min_num_buffers + 1, + max_buffers: 32, + crop: Crop { + left: visible_rect_left as u32, + top: visible_rect_top as u32, + width: rect_width, + height: rect_height, + }, + plane_formats, + // No need to set `frame_rate`, as it's only for the encoder. + ..Default::default() + }; + } + + fn handle_picture_ready( + &mut self, + buffer_id: FrameBufferId, + left: i32, + top: i32, + right: i32, + bottom: i32, + ) -> Option<ResourceId> { + let plane_size = ((right - left) * (bottom - top)) as u32; + for fmt in self.out_params.plane_formats.iter_mut() { + fmt.plane_size = plane_size; + // We don't need to set `plane_formats[i].stride` for the decoder. + } + + let resource_id: OutputResourceId = match self.frame_buf_id_to_res_id.get(&buffer_id) { + Some(id) => *id, + None => { + error!( + "unknown frame buffer id {} for stream {:?}", + buffer_id, self.stream_id + ); + return None; + } + }; + + Some(resource_id) + } + + fn handle_notify_end_of_bitstream_buffer(&mut self, bitstream_id: i32) -> Option<ResourceId> { + // `bitstream_id` in libvda is a timestamp passed via RESOURCE_QUEUE for the input buffer + // in second. + let timestamp: u64 = (bitstream_id as u64) * 1_000_000_000; + self.timestamp_to_input_res_id + .remove(&(timestamp as u64)) + .or_else(|| { + error!("failed to remove a timestamp {}", timestamp); + None + }) + } + + fn handle_reset_response(&mut self) -> Option<QueueType> { + if self.pending_clear_cmds.input { + self.pending_clear_cmds.input = false; + Some(QueueType::Input) + } else if self.pending_clear_cmds.output { + self.pending_clear_cmds.output = false; + + Some(QueueType::Output) + } else { + error!("unexpected ResetResponse"); + None + } + } +} + +/// A thin wrapper of a map of contexts with error handlings. +#[derive(Default)] +struct ContextMap { + map: BTreeMap<StreamId, Context>, +} + +impl ContextMap { + fn insert(&mut self, ctx: Context) -> VideoResult<()> { + match self.map.entry(ctx.stream_id) { + Entry::Vacant(e) => { + e.insert(ctx); + Ok(()) + } + Entry::Occupied(_) => { + error!("session {} already exists", ctx.stream_id); + Err(VideoError::InvalidStreamId(ctx.stream_id)) + } + } + } + + fn get(&self, stream_id: &StreamId) -> VideoResult<&Context> { + self.map.get(&stream_id).ok_or_else(|| { + error!("failed to get context of stream {}", *stream_id); + VideoError::InvalidStreamId(*stream_id) + }) + } + + fn get_mut(&mut self, stream_id: &StreamId) -> VideoResult<&mut Context> { + self.map.get_mut(&stream_id).ok_or_else(|| { + error!("failed to get context of stream {}", *stream_id); + VideoError::InvalidStreamId(*stream_id) + }) + } +} + +/// A thin wrapper of a map of libvda sesssions with error handlings. +#[derive(Default)] +struct SessionMap<'a> { + map: BTreeMap<u32, libvda::Session<'a>>, +} + +impl<'a> SessionMap<'a> { + fn contains_key(&self, stream_id: StreamId) -> bool { + self.map.contains_key(&stream_id) + } + + fn get(&self, stream_id: &StreamId) -> VideoResult<&libvda::Session<'a>> { + self.map.get(&stream_id).ok_or_else(|| { + error!("failed to get libvda session {}", *stream_id); + VideoError::InvalidStreamId(*stream_id) + }) + } + + fn get_mut(&mut self, stream_id: &StreamId) -> VideoResult<&mut libvda::Session<'a>> { + self.map.get_mut(&stream_id).ok_or_else(|| { + error!("failed to get libvda session {}", *stream_id); + VideoError::InvalidStreamId(*stream_id) + }) + } + + fn insert( + &mut self, + stream_id: StreamId, + session: libvda::Session<'a>, + ) -> Option<libvda::Session<'a>> { + self.map.insert(stream_id, session) + } +} + +/// Represents information of a decoder backed with `libvda`. +pub struct Decoder<'a> { + vda: &'a libvda::VdaInstance, + capability: Capability, + contexts: ContextMap, + sessions: SessionMap<'a>, +} + +impl<'a> Decoder<'a> { + pub fn new(vda: &'a libvda::VdaInstance) -> Self { + let capability = Capability::new(vda.get_capabilities()); + Decoder { + vda, + capability, + contexts: Default::default(), + sessions: Default::default(), + } + } + + /* + * Functions processing virtio-video commands. + */ + + fn query_capabilities(&self, queue_type: QueueType) -> CmdResponse { + let descs = match queue_type { + QueueType::Input => self.capability.in_fmts.clone(), + QueueType::Output => self.capability.out_fmts.clone(), + }; + + CmdResponse::QueryCapability(descs) + } + + fn create_stream(&mut self, stream_id: StreamId, coded_format: Format) -> VideoResult<()> { + // Create an instance of `Context`. + // Note that `libvda::Session` will be created not here but at the first call of + // `ResourceCreate`. This is because we need to fix a coded format for it, which + // will be set by `SetParams`. + self.contexts.insert(Context::new(stream_id, coded_format)) + } + + fn destroy_stream(&mut self, stream_id: StreamId) { + if self.contexts.map.remove(&stream_id).is_none() { + error!("Tried to destroy an invalid stream context {}", stream_id); + } + + // Close a libVDA session, as closing will be done in `Drop` for `session`. + // Note that `sessions` doesn't have an instance for `stream_id` if the + // first `ResourceCreate` haven't been called yet. + self.sessions.map.remove(&stream_id); + } + + fn create_resource( + &mut self, + poll_ctx: &PollContext<Token>, + stream_id: StreamId, + queue_type: QueueType, + resource_id: ResourceId, + uuid: u128, + ) -> VideoResult<()> { + // Create a instance of `libvda::Session` at the first time `ResourceCreate` is + // called here. + if !self.sessions.contains_key(stream_id) { + let ctx = self.contexts.get(&stream_id)?; + let profile = match ctx.in_params.format { + Some(Format::VP8) => Ok(libvda::Profile::VP8), + Some(Format::VP9) => Ok(libvda::Profile::VP9Profile0), + Some(Format::H264) => Ok(libvda::Profile::H264), + Some(f) => { + error!("specified format is invalid for bitstream: {:?}", f); + Err(VideoError::InvalidParameter) + } + None => { + error!("bitstream format is not specified"); + Err(VideoError::InvalidParameter) + } + }?; + + let session = self.vda.open_session(profile).map_err(|e| { + error!( + "failed to open a session {} for {:?}: {}", + stream_id, profile, e + ); + VideoError::InvalidOperation + })?; + + poll_ctx + .add(session.pipe(), Token::EventFd { id: stream_id }) + .map_err(|e| { + error!( + "failed to add FD to poll context for session {}: {}", + stream_id, e + ); + VideoError::InvalidOperation + })?; + + self.sessions.insert(stream_id, session); + } + + self.contexts + .get_mut(&stream_id)? + .register_buffer(resource_id, &uuid); + + if queue_type == QueueType::Input { + return Ok(()); + }; + + // Set output_buffer_count when ResourceCreate is called for frame buffers for the + // first time. + let mut ctx = self.contexts.get_mut(&stream_id)?; + if !ctx.set_output_buffer_count { + const OUTPUT_BUFFER_COUNT: usize = 32; + + // Set the buffer count to the maximum value. + // TODO(b/1518105): This is a hack due to the lack of way of telling a number of + // frame buffers explictly in virtio-video v3 RFC. Once we have the way, + // set_output_buffer_count should be called with a value passed by the guest. + self.sessions + .get(&stream_id)? + .set_output_buffer_count(OUTPUT_BUFFER_COUNT) + .map_err(VideoError::VdaError)?; + ctx.set_output_buffer_count = true; + } + + // We assume ResourceCreate is not called to an output resource that is already + // imported to Chrome for now. + // TODO(keiichiw): We need to support this case for a guest client who may use + // arbitrary numbers of buffers. (e.g. C2V4L2Component in ARCVM) + // Such a client is valid as long as it uses at most 32 buffers at the same time. + if let Some(frame_buf_id) = ctx.res_id_to_frame_buf_id.get(&resource_id) { + error!( + "resource {} has already been imported to Chrome as a frame buffer {}", + resource_id, frame_buf_id + ); + return Err(VideoError::InvalidOperation); + } + + Ok(()) + } + + fn destroy_all_resources(&mut self, stream_id: StreamId) -> VideoResult<()> { + // Reset the associated context. + self.contexts.get_mut(&stream_id)?.reset(); + Ok(()) + } + + fn queue_input_resource( + &mut self, + resource_bridge: &ResourceRequestSocket, + stream_id: StreamId, + resource_id: ResourceId, + timestamp: u64, + data_sizes: Vec<u32>, + ) -> VideoResult<()> { + let session = self.sessions.get(&stream_id)?; + + if data_sizes.len() != 1 { + error!("num_data_sizes must be 1 but {}", data_sizes.len()); + return Err(VideoError::InvalidOperation); + } + + // Take an ownership of this file by `into_raw_fd()` as this file will be closed by libvda. + let fd = self + .contexts + .get_mut(&stream_id)? + .get_resource_info(resource_bridge, resource_id)? + .file + .into_raw_fd(); + + // Register a mapping of timestamp to resource_id + self.contexts + .get_mut(&stream_id)? + .timestamp_to_input_res_id + .insert(timestamp, resource_id); + + // While the virtio-video driver handles timestamps as nanoseconds, + // Chrome assumes per-second timestamps coming. So, we need a conversion from nsec + // to sec. + // Note that this value should not be an unix time stamp but a frame number that + // a guest passes to a driver as a 32-bit integer in our implementation. + // So, overflow must not happen in this conversion. + let ts_sec: i32 = (timestamp / 1_000_000_000) as i32; + session + .decode( + ts_sec, + fd, // fd + 0, // offset is always 0 due to the driver implementation. + data_sizes[0], // bytes_used + ) + .map_err(VideoError::VdaError)?; + + Ok(()) + } + + fn queue_output_resource( + &mut self, + resource_bridge: &ResourceRequestSocket, + stream_id: StreamId, + resource_id: ResourceId, + ) -> VideoResult<()> { + let session = self.sessions.get(&stream_id)?; + let ctx = self.contexts.get_mut(&stream_id)?; + + // Check if the current pixel format is set to NV12. + match ctx.out_params.format { + Some(Format::NV12) => (), // OK + Some(f) => { + error!( + "video decoder only supports NV12 as a frame format but {:?}", + f + ); + return Err(VideoError::InvalidOperation); + } + None => { + error!("output format is not set"); + return Err(VideoError::InvalidOperation); + } + }; + + if ctx.keep_notification_output_buffer.is_none() { + // Stores an output buffer to notify EOS. + ctx.keep_notification_output_buffer = Some(resource_id); + + // Don't enqueue this resource to Chrome. + return Ok(()); + } + + // In case a given resource has been imported to VDA, call + // `session.reuse_output_buffer()` and return a response. + // Otherwise, `session.use_output_buffer()` will be called below. + match ctx.res_id_to_frame_buf_id.get(&resource_id) { + Some(buffer_id) => { + session + .reuse_output_buffer(*buffer_id) + .map_err(VideoError::VdaError)?; + return Ok(()); + } + None => (), + }; + + let resource_info = ctx.get_resource_info(resource_bridge, resource_id)?; + let fd = resource_info.file.as_raw_fd(); + + // Take an ownership of `resource_info.file`. + // This file will be kept until the stream is destroyed. + self.contexts + .get_mut(&stream_id)? + .keep_resources + .push(resource_info.file); + + let planes = vec![ + libvda::FramePlane { + offset: resource_info.planes[0].offset as i32, + stride: resource_info.planes[0].stride as i32, + }, + libvda::FramePlane { + offset: resource_info.planes[1].offset as i32, + stride: resource_info.planes[1].stride as i32, + }, + ]; + + let buffer_id = self + .contexts + .get_mut(&stream_id)? + .register_queued_frame_buffer(resource_id); + + session + .use_output_buffer(buffer_id as i32, libvda::PixelFormat::NV12, fd, &planes) + .map_err(VideoError::VdaError)?; + + Ok(()) + } + + fn get_params(&self, stream_id: StreamId, queue_type: QueueType) -> VideoResult<Params> { + let ctx = self.contexts.get(&stream_id)?; + Ok(match queue_type { + QueueType::Input => ctx.in_params.clone(), + QueueType::Output => ctx.out_params.clone(), + }) + } + + fn set_params( + &mut self, + stream_id: StreamId, + queue_type: QueueType, + params: Params, + ) -> VideoResult<()> { + let ctx = self.contexts.get_mut(&stream_id)?; + match queue_type { + QueueType::Input => { + if self.sessions.contains_key(stream_id) { + error!("parameter for input cannot be changed once decoding started"); + return Err(VideoError::InvalidParameter); + } + + // Only a few parameters can be changed by the guest. + ctx.in_params.format = params.format; + ctx.in_params.plane_formats = params.plane_formats.clone(); + } + QueueType::Output => { + // The guest cannot update parameters for output queue in the decoder. + } + }; + Ok(()) + } + + fn query_control(&self, ctrl_type: QueryCtrlType) -> VideoResult<QueryCtrlResponse> { + self.capability.query_control(&ctrl_type).ok_or_else(|| { + error!("querying an unsupported control: {:?}", ctrl_type); + VideoError::InvalidArgument + }) + } + + fn get_control(&self, stream_id: StreamId, ctrl_type: CtrlType) -> VideoResult<CtrlVal> { + let ctx = self.contexts.get(&stream_id)?; + match ctrl_type { + CtrlType::Profile => { + let profile = match ctx.in_params.format { + Some(Format::VP8) => Profile::VP8Profile0, + Some(Format::VP9) => Profile::VP9Profile0, + Some(Format::H264) => Profile::H264Baseline, + Some(f) => { + error!("specified format is invalid: {:?}", f); + return Err(VideoError::InvalidArgument); + } + None => { + error!("bitstream format is not set"); + return Err(VideoError::InvalidArgument); + } + }; + + Ok(CtrlVal::Profile(profile)) + } + CtrlType::Level => { + let level = match ctx.in_params.format { + Some(Format::H264) => Level::H264_1_0, + Some(f) => { + error!("specified format has no level: {:?}", f); + return Err(VideoError::InvalidArgument); + } + None => { + error!("bitstream format is not set"); + return Err(VideoError::InvalidArgument); + } + }; + + Ok(CtrlVal::Level(level)) + } + t => { + error!("cannot get a control value: {:?}", t); + Err(VideoError::InvalidArgument) + } + } + } + + fn drain_stream(&mut self, stream_id: StreamId) -> VideoResult<()> { + self.sessions + .get(&stream_id)? + .flush() + .map_err(VideoError::VdaError)?; + Ok(()) + } + + fn clear_queue(&mut self, stream_id: StreamId, queue_type: QueueType) -> VideoResult<()> { + let ctx = self.contexts.get_mut(&stream_id)?; + let session = self.sessions.get(&stream_id)?; + + let pending_clear_cmd = match queue_type { + QueueType::Input => &mut ctx.pending_clear_cmds.input, + QueueType::Output => &mut ctx.pending_clear_cmds.output, + }; + + if *pending_clear_cmd { + error!("Clear command is already in process"); + return Err(VideoError::InvalidOperation); + } + + // TODO(b/153406792): Though QUEUE_CLEAR is defined as a per-queue command in the + // specification, Chrome VDA's `reset()` clears both input and output buffers. + // So, this code can be a problem when a guest application wants to reset only one + // queue by REQBUFS(0). + // To handle this problem, we need to + // (i) update libvda interface or, + // (ii) re-enqueue discarded requests that were pending for the other direction. + session.reset().map_err(VideoError::VdaError)?; + + *pending_clear_cmd = true; + + Ok(()) + } +} + +impl<'a> Device for Decoder<'a> { + fn process_cmd( + &mut self, + cmd: VideoCmd, + poll_ctx: &PollContext<Token>, + resource_bridge: &ResourceRequestSocket, + ) -> VideoResult<VideoCmdResponseType> { + use VideoCmd::*; + use VideoCmdResponseType::{Async, Sync}; + + match cmd { + QueryCapability { queue_type } => Ok(Sync(self.query_capabilities(queue_type))), + StreamCreate { + stream_id, + coded_format, + } => { + self.create_stream(stream_id, coded_format)?; + Ok(Sync(CmdResponse::NoData)) + } + StreamDestroy { stream_id } => { + self.destroy_stream(stream_id); + Ok(Sync(CmdResponse::NoData)) + } + ResourceCreate { + stream_id, + queue_type, + resource_id, + uuid, + // ignore `plane_offsets` as we use `resource_info` given by `resource_bridge` instead. + .. + } => { + self.create_resource(poll_ctx, stream_id, queue_type, resource_id, uuid)?; + Ok(Sync(CmdResponse::NoData)) + } + ResourceDestroyAll { stream_id } => { + self.destroy_all_resources(stream_id)?; + Ok(Sync(CmdResponse::NoData)) + } + ResourceQueue { + stream_id, + queue_type: QueueType::Input, + resource_id, + timestamp, + data_sizes, + } => { + self.queue_input_resource( + resource_bridge, + stream_id, + resource_id, + timestamp, + data_sizes, + )?; + Ok(Async(AsyncCmdTag::Queue { + stream_id, + queue_type: QueueType::Input, + resource_id, + })) + } + ResourceQueue { + stream_id, + queue_type: QueueType::Output, + resource_id, + .. + } => { + self.queue_output_resource(resource_bridge, stream_id, resource_id)?; + Ok(Async(AsyncCmdTag::Queue { + stream_id, + queue_type: QueueType::Output, + resource_id, + })) + } + GetParams { + stream_id, + queue_type, + } => { + let params = self.get_params(stream_id, queue_type)?; + Ok(Sync(CmdResponse::GetParams { queue_type, params })) + } + SetParams { + stream_id, + queue_type, + params, + } => { + self.set_params(stream_id, queue_type, params)?; + Ok(Sync(CmdResponse::NoData)) + } + QueryControl { query_ctrl_type } => { + let resp = self.query_control(query_ctrl_type)?; + Ok(Sync(CmdResponse::QueryControl(resp))) + } + GetControl { + stream_id, + ctrl_type, + } => { + let ctrl_val = self.get_control(stream_id, ctrl_type)?; + Ok(Sync(CmdResponse::GetControl(ctrl_val))) + } + SetControl { .. } => { + error!("SET_CONTROL is not allowed for decoder"); + Err(VideoError::InvalidOperation) + } + StreamDrain { stream_id } => { + self.drain_stream(stream_id)?; + Ok(Async(AsyncCmdTag::Drain { stream_id })) + } + QueueClear { + stream_id, + queue_type, + } => { + self.clear_queue(stream_id, queue_type)?; + Ok(Async(AsyncCmdTag::Clear { + stream_id, + queue_type, + })) + } + } + } + + fn process_event_fd(&mut self, stream_id: u32) -> Option<VideoEvtResponseType> { + use crate::virtio::video::device::VideoEvtResponseType::*; + use libvda::Event::*; + + let session = match self.sessions.get_mut(&stream_id) { + Ok(s) => s, + Err(_) => { + error!("an event notified for an unknown session {}", stream_id); + return None; + } + }; + + let event = match session.read_event() { + Ok(event) => event, + Err(e) => { + error!("failed to read an event from session {}: {}", stream_id, e); + return None; + } + }; + + let ctx = match self.contexts.get_mut(&stream_id) { + Ok(ctx) => ctx, + Err(_) => { + error!( + "failed to get a context for session {}: {:?}", + stream_id, event + ); + return None; + } + }; + + match event { + ProvidePictureBuffers { + min_num_buffers, + width, + height, + visible_rect_left, + visible_rect_top, + visible_rect_right, + visible_rect_bottom, + } => { + ctx.handle_provide_picture_buffers( + min_num_buffers, + width, + height, + visible_rect_left, + visible_rect_top, + visible_rect_right, + visible_rect_bottom, + ); + Some(Event(VideoEvt { + typ: EvtType::DecResChanged, + stream_id, + })) + } + PictureReady { + buffer_id, // FrameBufferId + bitstream_id: ts_sec, + left, + top, + right, + bottom, + } => { + let resource_id = ctx.handle_picture_ready(buffer_id, left, top, right, bottom)?; + Some(AsyncCmd { + tag: AsyncCmdTag::Queue { + stream_id, + queue_type: QueueType::Output, + resource_id, + }, + resp: Ok(CmdResponse::ResourceQueue { + // Conversion from sec to nsec. + timestamp: (ts_sec as u64) * 1_000_000_000, + // TODO(b/149725148): Set buffer flags once libvda exposes them. + flags: 0, + // `size` is only used for the encoder. + size: 0, + }), + }) + } + NotifyEndOfBitstreamBuffer { bitstream_id } => { + let resource_id = ctx.handle_notify_end_of_bitstream_buffer(bitstream_id)?; + Some(AsyncCmd { + tag: AsyncCmdTag::Queue { + stream_id, + queue_type: QueueType::Input, + resource_id, + }, + resp: Ok(CmdResponse::ResourceQueue { + timestamp: 0, // ignored for bitstream buffers. + flags: 0, // no flag is raised, as it's returned successfully. + size: 0, // this field is only for encoder + }), + }) + } + FlushResponse(resp) => { + let tag = AsyncCmdTag::Drain { stream_id }; + match resp { + libvda::Response::Success => Some(AsyncCmd { + tag, + resp: Ok(CmdResponse::NoData), + }), + _ => { + // TODO(b/151810591): If `resp` is `libvda::Response::Canceled`, + // we should notify it to the driver in some way. + error!("failed to 'Flush' in VDA: {:?}", resp); + Some(AsyncCmd { + tag, + resp: Err(VideoError::VdaFailure(resp)), + }) + } + } + } + ResetResponse(resp) => { + let tag = AsyncCmdTag::Clear { + stream_id, + queue_type: ctx.handle_reset_response()?, + }; + match resp { + libvda::Response::Success => Some(AsyncCmd { + tag, + resp: Ok(CmdResponse::NoData), + }), + _ => { + error!("failed to 'Reset' in VDA: {:?}", resp); + Some(AsyncCmd { + tag, + resp: Err(VideoError::VdaFailure(resp)), + }) + } + } + } + NotifyError(resp) => { + error!("an error is notified by VDA: {}", resp); + Some(Event(VideoEvt { + typ: EvtType::Error, + stream_id, + })) + } + } + } + + fn take_resource_id_to_notify_eos(&mut self, stream_id: u32) -> Option<u32> { + self.contexts + .get_mut(&stream_id) + .ok() + .and_then(|s| s.keep_notification_output_buffer.take()) + } +} diff --git a/devices/src/virtio/video/device.rs b/devices/src/virtio/video/device.rs new file mode 100644 index 0000000..b9d80d6 --- /dev/null +++ b/devices/src/virtio/video/device.rs @@ -0,0 +1,91 @@ +// 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. + +//! Definition of the trait `Device` that each backend video device must implement. + +use sys_util::{PollContext, PollToken}; + +use crate::virtio::resource_bridge::ResourceRequestSocket; +use crate::virtio::video::command::{QueueType, VideoCmd}; +use crate::virtio::video::error::*; +use crate::virtio::video::event::VideoEvt; +use crate::virtio::video::response; + +#[derive(PollToken, Debug)] +pub enum Token { + CmdQueue, + EventQueue, + EventFd { id: u32 }, + Kill, + InterruptResample, +} + +/// A tag for commands being processed asynchronously in the back-end device. +/// TODO(b/149720783): Remove this enum by using async primitives. +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug)] +pub enum AsyncCmdTag { + Queue { + stream_id: u32, + queue_type: QueueType, + resource_id: u32, + }, + Drain { + stream_id: u32, + }, + Clear { + stream_id: u32, + queue_type: QueueType, + }, +} + +/// A return value when a command from the guest is processed. +#[derive(Debug)] +pub enum VideoCmdResponseType { + /// The response for a synchronous command. This can be returned to the guest immediately via + /// command virtqueue. + Sync(response::CmdResponse), + /// The tag for an asynchronous command that the back-end device will complete. + /// Once the command is completed, its result will be sent with the same tag. + /// This can be seen as a poor man's future pattern. + Async(AsyncCmdTag), +} + +/// A return value when processing a event the back-end device sent. +#[derive(Debug)] +pub enum VideoEvtResponseType { + /// The response for an asynchronous command that was enqueued through `process_cmd` before. + /// The `tag` must be same as the one returned when the command is enqueued. + AsyncCmd { + tag: AsyncCmdTag, + resp: VideoResult<response::CmdResponse>, + }, + /// The event that happened in the back-end device. + Event(VideoEvt), +} + +pub trait Device { + /// Processes a virtio-video command. + /// If the command expects a synchronous response, it returns a response as `VideoCmdResponseType::Sync`. + /// Otherwise, it returns a name of the descriptor chain that will be used when a response is prepared. + /// Implementations of this method is passed a PollContext object which can be used to add or remove + /// FDs to poll. It is expected that only Token::EventFd items would be added. When a Token::EventFd + /// event arrives, process_event_fd() will be invoked. + /// TODO(b/149720783): Make this an async function. + fn process_cmd( + &mut self, + cmd: VideoCmd, + poll_ctx: &PollContext<Token>, + resource_bridge: &ResourceRequestSocket, + ) -> VideoResult<VideoCmdResponseType>; + + /// Processes an available Token::EventFd event. + /// If the message is sent via commandq, the return value is `VideoEvtResponseType::AsyncCmd`. + /// Otherwise (i.e. case of eventq), it's `VideoEvtResponseType::Event`. + /// TODO(b/149720783): Make this an async function. + fn process_event_fd(&mut self, stream_id: u32) -> Option<VideoEvtResponseType>; + + /// Returns an ID for an available output resource that can be used to notify EOS. + /// Note that this resource must be enqueued by `ResourceQueue` and not be returned yet. + fn take_resource_id_to_notify_eos(&mut self, stream_id: u32) -> Option<u32>; +} diff --git a/devices/src/virtio/video/encoder/mod.rs b/devices/src/virtio/video/encoder/mod.rs new file mode 100644 index 0000000..d6b8cef --- /dev/null +++ b/devices/src/virtio/video/encoder/mod.rs @@ -0,0 +1,40 @@ +// 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. + +//! Implementation of the the `Encoder` struct, which is responsible for translation between the +//! virtio protocols and LibVDA APIs. + +use sys_util::PollContext; + +use crate::virtio::resource_bridge::ResourceRequestSocket; +use crate::virtio::video::command::VideoCmd; +use crate::virtio::video::device::{Device, Token, VideoCmdResponseType, VideoEvtResponseType}; +use crate::virtio::video::error::*; + +pub struct Encoder; + +impl Encoder { + pub fn new() -> Self { + Encoder {} + } +} + +impl Device for Encoder { + fn process_cmd( + &mut self, + _cmd: VideoCmd, + _poll_ctx: &PollContext<Token>, + _resource_bridge: &ResourceRequestSocket, + ) -> VideoResult<VideoCmdResponseType> { + Err(VideoError::InvalidOperation) + } + + fn process_event_fd(&mut self, _stream_id: u32) -> Option<VideoEvtResponseType> { + None + } + + fn take_resource_id_to_notify_eos(&mut self, _stream_id: u32) -> Option<u32> { + None + } +} diff --git a/devices/src/virtio/video/error.rs b/devices/src/virtio/video/error.rs new file mode 100644 index 0000000..5357c1f --- /dev/null +++ b/devices/src/virtio/video/error.rs @@ -0,0 +1,83 @@ +// 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. + +//! Errors that can happen while encoding or decoding. + +use std::fmt; +use std::io; + +use data_model::Le32; + +use crate::virtio::resource_bridge::ResourceBridgeError; +use crate::virtio::video::protocol::*; +use crate::virtio::video::response::Response; +use crate::virtio::Writer; + +/// An error indicating something went wrong while encoding or decoding. +/// Unlike `virtio::video::Error`, `VideoError` is not fatal for `Worker`. +#[derive(Debug)] +pub enum VideoError { + /// Invalid argument. + InvalidArgument, + /// Invalid operation + InvalidOperation, + /// Invalid stream ID is specified. + InvalidStreamId(u32), + /// Invalid resource ID is specified. + InvalidResourceId { stream_id: u32, resource_id: u32 }, + /// Invalid parameters are specified. + InvalidParameter, + /// Failed to get a resource FD via resource_bridge. + ResourceBridgeFailure(ResourceBridgeError), + /// `libvda` returned an error. + VdaError(libvda::Error), + /// `libvda` returned a failure response. + VdaFailure(libvda::Response), +} + +impl fmt::Display for VideoError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::VideoError::*; + match self { + InvalidArgument => write!(f, "invalid argument"), + InvalidOperation => write!(f, "invalid operation"), + InvalidStreamId(id) => write!(f, "invalid stream ID {}", id), + InvalidResourceId { + stream_id, + resource_id, + } => write!( + f, + "invalid resource ID {} for stream {}", + resource_id, stream_id + ), + InvalidParameter => write!(f, "invalid parameter"), + ResourceBridgeFailure(id) => write!(f, "failed to get resource FD for id {}", id), + VdaError(e) => write!(f, "error occurred in libvda: {}", e), + VdaFailure(r) => write!(f, "failed while processing a requst in VDA: {}", r), + } + } +} + +impl std::error::Error for VideoError {} + +pub type VideoResult<T> = Result<T, VideoError>; + +impl Response for VideoError { + fn write(&self, w: &mut Writer) -> Result<(), io::Error> { + use VideoError::*; + + let type_ = Le32::from(match *self { + InvalidResourceId { .. } => VIRTIO_VIDEO_RESP_ERR_INVALID_RESOURCE_ID, + InvalidStreamId(_) => VIRTIO_VIDEO_RESP_ERR_INVALID_STREAM_ID, + InvalidParameter => VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER, + // TODO(b/1518105): Add more detailed error code if a new protocol supports ones. + _ => VIRTIO_VIDEO_RESP_ERR_INVALID_OPERATION, + }); + + w.write_obj(virtio_video_cmd_hdr { + type_, + ..Default::default() + }) + } +} diff --git a/devices/src/virtio/video/event.rs b/devices/src/virtio/video/event.rs new file mode 100644 index 0000000..072f353 --- /dev/null +++ b/devices/src/virtio/video/event.rs @@ -0,0 +1,35 @@ +// 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. + +//! Events can happen in virtio video devices. + +use std::io; + +use data_model::Le32; +use enumn::N; + +use crate::virtio::video::protocol::*; +use crate::virtio::video::response::Response; +use crate::virtio::Writer; + +#[derive(Debug, Copy, Clone, N)] +pub enum EvtType { + Error = VIRTIO_VIDEO_EVENT_ERROR as isize, + DecResChanged = VIRTIO_VIDEO_EVENT_DECODER_RESOLUTION_CHANGED as isize, +} + +#[derive(Debug, Clone)] +pub struct VideoEvt { + pub typ: EvtType, + pub stream_id: u32, +} + +impl Response for VideoEvt { + fn write(&self, w: &mut Writer) -> Result<(), io::Error> { + w.write_obj(virtio_video_event { + event_type: Le32::from(self.typ as u32), + stream_id: Le32::from(self.stream_id), + }) + } +} diff --git a/devices/src/virtio/video/format.rs b/devices/src/virtio/video/format.rs new file mode 100644 index 0000000..fc2c441 --- /dev/null +++ b/devices/src/virtio/video/format.rs @@ -0,0 +1,116 @@ +// 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. + +//! Data structures that represent video format information in virtio video devices. + +use std::convert::{From, Into, TryFrom}; +use std::io; + +use data_model::Le32; +use enumn::N; +use sys_util::error; + +use crate::virtio::video::command::ReadCmdError; +use crate::virtio::video::protocol::*; +use crate::virtio::video::response::Response; +use crate::virtio::Writer; + +#[derive(PartialEq, Eq, PartialOrd, Ord, N, Clone, Copy, Debug)] +#[repr(u32)] +pub enum Profile { + VP8Profile0 = VIRTIO_VIDEO_PROFILE_VP8_PROFILE0, + VP9Profile0 = VIRTIO_VIDEO_PROFILE_VP9_PROFILE0, + H264Baseline = VIRTIO_VIDEO_PROFILE_H264_BASELINE, +} +impl_try_from_le32_for_enumn!(Profile, "profile"); + +#[derive(PartialEq, Eq, PartialOrd, Ord, N, Clone, Copy, Debug)] +#[repr(u32)] +pub enum Level { + H264_1_0 = VIRTIO_VIDEO_LEVEL_H264_1_0, +} +impl_try_from_le32_for_enumn!(Level, "level"); + +#[derive(PartialEq, Eq, PartialOrd, Ord, N, Clone, Copy, Debug)] +#[repr(u32)] +pub enum Format { + // Raw formats + NV12 = VIRTIO_VIDEO_FORMAT_NV12, + YUV420 = VIRTIO_VIDEO_FORMAT_YUV420, + + // Bitstream formats + H264 = VIRTIO_VIDEO_FORMAT_H264, + VP8 = VIRTIO_VIDEO_FORMAT_VP8, + VP9 = VIRTIO_VIDEO_FORMAT_VP9, +} +impl_try_from_le32_for_enumn!(Format, "format"); + +#[derive(Debug, Default, Copy, Clone)] +pub struct Crop { + pub left: u32, + pub top: u32, + pub width: u32, + pub height: u32, +} +impl_from_for_interconvertible_structs!(virtio_video_crop, Crop, left, top, width, height); + +#[derive(Debug, Default, Clone, Copy)] +pub struct PlaneFormat { + pub plane_size: u32, + pub stride: u32, +} +impl_from_for_interconvertible_structs!(virtio_video_plane_format, PlaneFormat, plane_size, stride); + +#[derive(Debug, Default, Clone, Copy)] +pub struct FormatRange { + pub min: u32, + pub max: u32, + pub step: u32, +} +impl_from_for_interconvertible_structs!(virtio_video_format_range, FormatRange, min, max, step); + +#[derive(Debug, Default, Clone)] +pub struct FrameFormat { + pub width: FormatRange, + pub height: FormatRange, + pub bitrates: Vec<FormatRange>, +} + +impl Response for FrameFormat { + fn write(&self, w: &mut Writer) -> Result<(), io::Error> { + w.write_obj(virtio_video_format_frame { + width: self.width.into(), + height: self.height.into(), + num_rates: Le32::from(self.bitrates.len() as u32), + ..Default::default() + })?; + w.write_iter( + self.bitrates + .iter() + .map(|r| Into::<virtio_video_format_range>::into(*r)), + ) + } +} + +#[derive(Debug, Clone)] +pub struct FormatDesc { + pub mask: u64, + pub format: Format, + pub frame_formats: Vec<FrameFormat>, +} + +impl Response for FormatDesc { + fn write(&self, w: &mut Writer) -> Result<(), io::Error> { + w.write_obj(virtio_video_format_desc { + mask: self.mask.into(), + format: Le32::from(self.format as u32), + // ChromeOS only supports single-buffer mode. + planes_layout: Le32::from(VIRTIO_VIDEO_PLANES_LAYOUT_SINGLE_BUFFER), + // No alignment is required on boards that we currently support. + plane_align: Le32::from(0), + num_frames: Le32::from(self.frame_formats.len() as u32), + })?; + self.frame_formats.iter().map(|ff| ff.write(w)).collect() + } +} diff --git a/devices/src/virtio/video/macros.rs b/devices/src/virtio/video/macros.rs new file mode 100644 index 0000000..d1ed386 --- /dev/null +++ b/devices/src/virtio/video/macros.rs @@ -0,0 +1,46 @@ +// 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. + +//! Macros that helps virtio video implementation. + +/// Implements TryFrom<data_model::Le32> for an enum that implements `enumn::N`. +#[macro_export] +macro_rules! impl_try_from_le32_for_enumn { + ($ty:ty, $name:literal) => { + impl TryFrom<Le32> for $ty { + type Error = ReadCmdError; + + fn try_from(x: Le32) -> Result<Self, Self::Error> { + let v: u32 = x.into(); + Self::n(v).ok_or_else(|| { + error!(concat!("invalid ", $name, ": {}"), v); + ReadCmdError::InvalidArgument + }) + } + } + }; +} + +/// Implements `From` between two structs whose each field implements `From` each other. +#[macro_export] +macro_rules! impl_from_for_interconvertible_structs { + ($t1:ident, $t2:ident, $($v:ident),+) => { + impl_from_for_interconvertible_structs_core!($t1, $t2, $( $v ),+ ); + impl_from_for_interconvertible_structs_core!($t2, $t1, $( $v ),+ ); + }; +} + +macro_rules! impl_from_for_interconvertible_structs_core { + ($t1:ident, $t2:ident, $($v:ident),+) => { + impl From<$t1> for $t2 { + #[allow(clippy::needless_update)] + fn from(x :$t1) -> Self { + $t2 { + $( $v: x.$v.into(), )+ + ..Default::default() // for paddings + } + } + } + }; +} diff --git a/devices/src/virtio/video/mod.rs b/devices/src/virtio/video/mod.rs new file mode 100644 index 0000000..1b3058a --- /dev/null +++ b/devices/src/virtio/video/mod.rs @@ -0,0 +1,255 @@ +// 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. + +//! This module implements the virtio video encoder and decoder devices. +//! The current implementation uses [v3 RFC] of the virtio-video protocol. +//! +//! [v3 RFC]: https://markmail.org/thread/wxdne5re7aaugbjg + +use std::fmt::{self, Display}; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::thread; + +use data_model::{DataInit, Le32}; +use sys_util::{error, Error as SysError, EventFd, GuestMemory}; + +use crate::virtio::resource_bridge::ResourceRequestSocket; +use crate::virtio::virtio_device::VirtioDevice; +use crate::virtio::{self, copy_config, DescriptorError, Interrupt, VIRTIO_F_VERSION_1}; + +#[macro_use] +mod macros; +mod command; +mod control; +mod decoder; +mod device; +mod encoder; +mod error; +mod event; +mod format; +mod params; +mod protocol; +mod response; +mod worker; + +use command::ReadCmdError; +use device::AsyncCmdTag; +use worker::Worker; + +const QUEUE_SIZE: u16 = 256; +const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE, QUEUE_SIZE]; + +/// An error indicating something went wrong in virtio-video's worker. +#[derive(Debug)] +pub enum Error { + /// Failed to create a libvda instance. + LibvdaCreationFailed(libvda::Error), + /// Creating PollContext failed. + PollContextCreationFailed(SysError), + /// A DescriptorChain contains invalid data. + InvalidDescriptorChain(DescriptorError), + /// Invalid output buffer is specified for EOS notification. + InvalidEOSResource { stream_id: u32, resource_id: u32 }, + /// No available descriptor in which an event is written to. + DescriptorNotAvailable, + /// Output buffer for EOS is unavailable. + NoEOSBuffer { stream_id: u32 }, + /// Error while polling for events. + PollError(SysError), + /// Failed to read a virtio-video command. + ReadFailure(ReadCmdError), + /// Got response for an unexpected asynchronous command. + UnexpectedResponse(AsyncCmdTag), + /// Failed to write an event into the event queue. + WriteEventFailure { + event: event::VideoEvt, + error: std::io::Error, + }, +} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use Error::*; + match self { + LibvdaCreationFailed(e) => write!(f, "failed to create a libvda instance: {}", e), + PollContextCreationFailed(e) => write!(f, "failed to create PollContext: {}", e), + InvalidDescriptorChain(e) => write!(f, "DescriptorChain contains invalid data: {}", e), + InvalidEOSResource { + stream_id, + resource_id, + } => write!( + f, + "invalid resource {} was specified for stream {}'s EOS", + resource_id, stream_id + ), + DescriptorNotAvailable => { + write!(f, "no available descriptor in which an event is written to") + } + NoEOSBuffer { stream_id } => write!( + f, + "no output resource is available to notify EOS: {}", + stream_id + ), + PollError(err) => write!(f, "failed to poll events: {}", err), + ReadFailure(e) => write!(f, "failed to read a command from the guest: {}", e), + UnexpectedResponse(tag) => { + write!(f, "got a response for an untracked command: {:?}", tag) + } + WriteEventFailure { event, error } => write!( + f, + "failed to write an event {:?} into event queue: {}", + event, error + ), + } + } +} + +impl std::error::Error for Error {} +pub type Result<T> = std::result::Result<T, Error>; + +#[derive(Debug)] +pub enum VideoDeviceType { + Decoder, + Encoder, +} + +pub struct VideoDevice { + device_type: VideoDeviceType, + kill_evt: Option<EventFd>, + resource_bridge: Option<ResourceRequestSocket>, +} + +impl VideoDevice { + pub fn new( + device_type: VideoDeviceType, + resource_bridge: Option<ResourceRequestSocket>, + ) -> VideoDevice { + VideoDevice { + device_type, + kill_evt: None, + resource_bridge, + } + } +} + +impl Drop for VideoDevice { + 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); + } + } +} + +impl VirtioDevice for VideoDevice { + fn keep_fds(&self) -> Vec<RawFd> { + let mut keep_fds = Vec::new(); + if let Some(resource_bridge) = &self.resource_bridge { + keep_fds.push(resource_bridge.as_raw_fd()); + } + keep_fds + } + + fn device_type(&self) -> u32 { + match &self.device_type { + VideoDeviceType::Decoder => virtio::TYPE_VIDEO_DEC, + VideoDeviceType::Encoder => virtio::TYPE_VIDEO_ENC, + } + } + + fn queue_max_sizes(&self) -> &[u16] { + QUEUE_SIZES + } + + fn features(&self) -> u64 { + 1u64 << VIRTIO_F_VERSION_1 + | 1u64 << protocol::VIRTIO_VIDEO_F_RESOURCE_NON_CONTIG + | 1u64 << protocol::VIRTIO_VIDEO_F_RESOURCE_VIRTIO_OBJECT + } + + fn read_config(&self, offset: u64, data: &mut [u8]) { + let mut cfg = protocol::virtio_video_config { + version: Le32::from(0), + max_caps_length: Le32::from(1024), // Set a big number + max_resp_length: Le32::from(1024), // Set a big number + }; + copy_config(data, 0, cfg.as_mut_slice(), offset); + } + + fn activate( + &mut self, + mem: GuestMemory, + interrupt: Interrupt, + mut queues: Vec<virtio::queue::Queue>, + mut queue_evts: Vec<EventFd>, + ) { + if queues.len() != QUEUE_SIZES.len() { + error!( + "wrong number of queues are passed: expected {}, actual {}", + queues.len(), + QUEUE_SIZES.len() + ); + return; + } + if queue_evts.len() != QUEUE_SIZES.len() { + error!( + "wrong number of event FDs are passed: expected {}, actual {}", + queue_evts.len(), + QUEUE_SIZES.len() + ); + } + + let (self_kill_evt, kill_evt) = match EventFd::new().and_then(|e| Ok((e.try_clone()?, e))) { + Ok(v) => v, + Err(e) => { + error!("failed to create kill EventFd pair: {:?}", e); + return; + } + }; + self.kill_evt = Some(self_kill_evt); + + let cmd_queue = queues.remove(0); + let cmd_evt = queue_evts.remove(0); + let event_queue = queues.remove(0); + let event_evt = queue_evts.remove(0); + let resource_bridge = match self.resource_bridge.take() { + Some(r) => r, + None => { + error!("no resource bridge is passed"); + return; + } + }; + let mut worker = Worker { + interrupt, + mem, + cmd_evt, + event_evt, + kill_evt, + resource_bridge, + }; + let worker_result = match &self.device_type { + VideoDeviceType::Decoder => thread::Builder::new() + .name("virtio video decoder".to_owned()) + .spawn(move || { + let vda = libvda::VdaInstance::new(libvda::VdaImplType::Gavda) + .map_err(Error::LibvdaCreationFailed)?; + let device = decoder::Decoder::new(&vda); + worker.run(cmd_queue, event_queue, device) + }), + VideoDeviceType::Encoder => thread::Builder::new() + .name("virtio video encoder".to_owned()) + .spawn(move || { + let device = encoder::Encoder::new(); + worker.run(cmd_queue, event_queue, device) + }), + }; + if let Err(e) = worker_result { + error!( + "failed to spawn virtio_video worker for {:?}: {}", + &self.device_type, e + ); + return; + } + } +} diff --git a/devices/src/virtio/video/params.rs b/devices/src/virtio/video/params.rs new file mode 100644 index 0000000..e4bb614 --- /dev/null +++ b/devices/src/virtio/video/params.rs @@ -0,0 +1,111 @@ +// 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. + +//! Parameters for streams in virtio video devices. + +use std::convert::{From, Into, TryFrom}; + +use data_model::Le32; +use sys_util::error; + +use crate::virtio::video::command::{QueueType, ReadCmdError}; +use crate::virtio::video::format::*; +use crate::virtio::video::protocol::*; + +/// Safe wrapper of `virtio_video_params`. +/// Note that this struct doesn't have a field corresponding to `queue_type` in +/// `virtio_video_params`. The type of queue should be stored by one that has an `Params` instance. +#[derive(Debug, Default, Clone)] +pub struct Params { + // Use `Option<Format>` instead of `Format` because an image format may not be determined until + // video decoding is started in the decoder. + pub format: Option<Format>, + pub frame_width: u32, + pub frame_height: u32, + pub min_buffers: u32, + pub max_buffers: u32, + pub crop: Crop, + pub frame_rate: u32, + pub plane_formats: Vec<PlaneFormat>, +} + +impl TryFrom<virtio_video_params> for Params { + type Error = ReadCmdError; + + fn try_from( + virtio_video_params { + format, + frame_width, + frame_height, + min_buffers, + max_buffers, + crop, + frame_rate, + num_planes, + plane_formats, + .. + }: virtio_video_params, + ) -> Result<Self, Self::Error> { + let num_planes = Into::<u32>::into(num_planes); // as usize; + if num_planes as usize > plane_formats.len() { + error!( + "num_planes must not exceed {} but {}", + plane_formats.len(), + Into::<u32>::into(num_planes) + ); + return Err(ReadCmdError::InvalidArgument); + } + let plane_formats = plane_formats[0..num_planes as usize] + .iter() + .map(|x| Into::<PlaneFormat>::into(*x)) + .collect::<Vec<_>>(); + + Ok(Params { + format: Format::n(format.into()), + frame_width: frame_width.into(), + frame_height: frame_height.into(), + min_buffers: min_buffers.into(), + max_buffers: max_buffers.into(), + crop: crop.into(), + frame_rate: frame_rate.into(), + plane_formats, + }) + } +} + +impl Params { + pub fn to_virtio_video_params(&self, queue_type: QueueType) -> virtio_video_params { + let Params { + format, + frame_width, + frame_height, + min_buffers, + max_buffers, + crop, + frame_rate, + plane_formats, + } = self; + let num_planes = Le32::from(plane_formats.len() as u32); + let mut p_fmts: [virtio_video_plane_format; VIRTIO_VIDEO_MAX_PLANES as usize] = + Default::default(); + for (i, pf) in plane_formats.iter().enumerate() { + p_fmts[i] = Into::<virtio_video_plane_format>::into(*pf); + } + + virtio_video_params { + queue_type: (queue_type as u32).into(), + format: format + .map(|f| Le32::from(f as u32)) + .unwrap_or_else(|| Le32::from(0)), + frame_width: Le32::from(*frame_width), + frame_height: Le32::from(*frame_height), + min_buffers: Le32::from(*min_buffers), + max_buffers: Le32::from(*max_buffers), + crop: virtio_video_crop::from(*crop), + frame_rate: Le32::from(*frame_rate), + num_planes, + plane_formats: p_fmts, + } + } +} diff --git a/devices/src/virtio/video/protocol.rs b/devices/src/virtio/video/protocol.rs new file mode 100644 index 0000000..0e2106e --- /dev/null +++ b/devices/src/virtio/video/protocol.rs @@ -0,0 +1,487 @@ +// 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. + +//! This file was generated by the following commands and modified manually. +//! +//! ``` +//! $ bindgen virtio_video.h \ +//! --whitelist-type "virtio_video.*" \ +//! --whitelist-var "VIRTIO_VIDEO_.*" \ +//! --with-derive-default \ +//! --no-layout-tests \ +//! --no-prepend-enum-name > protocol.rs +//! $ sed -i 's/u/u/g' protocol.rs +//! $ sed -i 's/Le/Le/g' protocol.rs +//! ``` +//! +//! The main points of the manual modifications are as follows: +//! * Removed `hdr` from each command struct so that we can read the header and a command body separately. +//! (cf. [related discussion](https://markmail.org/message/tr5g6axqq2zzq64y)) +//! * Added implementations of DataInit for each struct. + +#![allow(dead_code, non_snake_case, non_camel_case_types)] + +use data_model::{DataInit, Le32, Le64}; + +pub const VIRTIO_VIDEO_F_RESOURCE_GUEST_PAGES: u32 = 0; +pub const VIRTIO_VIDEO_F_RESOURCE_NON_CONTIG: u32 = 1; +pub const VIRTIO_VIDEO_F_RESOURCE_VIRTIO_OBJECT: u32 = 2; +pub const VIRTIO_VIDEO_MAX_PLANES: u32 = 8; +pub const VIRTIO_VIDEO_FORMAT_RAW_MIN: virtio_video_format = 1; +pub const VIRTIO_VIDEO_FORMAT_ARGB8888: virtio_video_format = 1; +pub const VIRTIO_VIDEO_FORMAT_BGRA8888: virtio_video_format = 2; +pub const VIRTIO_VIDEO_FORMAT_NV12: virtio_video_format = 3; +pub const VIRTIO_VIDEO_FORMAT_YUV420: virtio_video_format = 4; +pub const VIRTIO_VIDEO_FORMAT_YVU420: virtio_video_format = 5; +pub const VIRTIO_VIDEO_FORMAT_RAW_MAX: virtio_video_format = 5; +pub const VIRTIO_VIDEO_FORMAT_CODED_MIN: virtio_video_format = 4096; +pub const VIRTIO_VIDEO_FORMAT_MPEG2: virtio_video_format = 4096; +pub const VIRTIO_VIDEO_FORMAT_MPEG4: virtio_video_format = 4097; +pub const VIRTIO_VIDEO_FORMAT_H264: virtio_video_format = 4098; +pub const VIRTIO_VIDEO_FORMAT_HEVC: virtio_video_format = 4099; +pub const VIRTIO_VIDEO_FORMAT_VP8: virtio_video_format = 4100; +pub const VIRTIO_VIDEO_FORMAT_VP9: virtio_video_format = 4101; +pub const VIRTIO_VIDEO_FORMAT_CODED_MAX: virtio_video_format = 4101; +pub type virtio_video_format = u32; +pub const VIRTIO_VIDEO_PROFILE_H264_MIN: virtio_video_profile = 256; +pub const VIRTIO_VIDEO_PROFILE_H264_BASELINE: virtio_video_profile = 256; +pub const VIRTIO_VIDEO_PROFILE_H264_MAIN: virtio_video_profile = 257; +pub const VIRTIO_VIDEO_PROFILE_H264_EXTENDED: virtio_video_profile = 258; +pub const VIRTIO_VIDEO_PROFILE_H264_HIGH: virtio_video_profile = 259; +pub const VIRTIO_VIDEO_PROFILE_H264_HIGH10PROFILE: virtio_video_profile = 260; +pub const VIRTIO_VIDEO_PROFILE_H264_HIGH422PROFILE: virtio_video_profile = 261; +pub const VIRTIO_VIDEO_PROFILE_H264_HIGH444PREDICTIVEPROFILE: virtio_video_profile = 262; +pub const VIRTIO_VIDEO_PROFILE_H264_SCALABLEBASELINE: virtio_video_profile = 263; +pub const VIRTIO_VIDEO_PROFILE_H264_SCALABLEHIGH: virtio_video_profile = 264; +pub const VIRTIO_VIDEO_PROFILE_H264_STEREOHIGH: virtio_video_profile = 265; +pub const VIRTIO_VIDEO_PROFILE_H264_MULTIVIEWHIGH: virtio_video_profile = 266; +pub const VIRTIO_VIDEO_PROFILE_H264_MAX: virtio_video_profile = 266; +pub const VIRTIO_VIDEO_PROFILE_HEVC_MIN: virtio_video_profile = 512; +pub const VIRTIO_VIDEO_PROFILE_HEVC_MAIN: virtio_video_profile = 512; +pub const VIRTIO_VIDEO_PROFILE_HEVC_MAIN10: virtio_video_profile = 513; +pub const VIRTIO_VIDEO_PROFILE_HEVC_MAIN_STILL_PICTURE: virtio_video_profile = 514; +pub const VIRTIO_VIDEO_PROFILE_HEVC_MAX: virtio_video_profile = 514; +pub const VIRTIO_VIDEO_PROFILE_VP8_MIN: virtio_video_profile = 768; +pub const VIRTIO_VIDEO_PROFILE_VP8_PROFILE0: virtio_video_profile = 768; +pub const VIRTIO_VIDEO_PROFILE_VP8_PROFILE1: virtio_video_profile = 769; +pub const VIRTIO_VIDEO_PROFILE_VP8_PROFILE2: virtio_video_profile = 770; +pub const VIRTIO_VIDEO_PROFILE_VP8_PROFILE3: virtio_video_profile = 771; +pub const VIRTIO_VIDEO_PROFILE_VP8_MAX: virtio_video_profile = 771; +pub const VIRTIO_VIDEO_PROFILE_VP9_MIN: virtio_video_profile = 1024; +pub const VIRTIO_VIDEO_PROFILE_VP9_PROFILE0: virtio_video_profile = 1024; +pub const VIRTIO_VIDEO_PROFILE_VP9_PROFILE1: virtio_video_profile = 1025; +pub const VIRTIO_VIDEO_PROFILE_VP9_PROFILE2: virtio_video_profile = 1026; +pub const VIRTIO_VIDEO_PROFILE_VP9_PROFILE3: virtio_video_profile = 1027; +pub const VIRTIO_VIDEO_PROFILE_VP9_MAX: virtio_video_profile = 1027; +pub type virtio_video_profile = u32; +pub const VIRTIO_VIDEO_LEVEL_H264_MIN: virtio_video_level = 256; +pub const VIRTIO_VIDEO_LEVEL_H264_1_0: virtio_video_level = 256; +pub const VIRTIO_VIDEO_LEVEL_H264_1_1: virtio_video_level = 257; +pub const VIRTIO_VIDEO_LEVEL_H264_1_2: virtio_video_level = 258; +pub const VIRTIO_VIDEO_LEVEL_H264_1_3: virtio_video_level = 259; +pub const VIRTIO_VIDEO_LEVEL_H264_2_0: virtio_video_level = 260; +pub const VIRTIO_VIDEO_LEVEL_H264_2_1: virtio_video_level = 261; +pub const VIRTIO_VIDEO_LEVEL_H264_2_2: virtio_video_level = 262; +pub const VIRTIO_VIDEO_LEVEL_H264_3_0: virtio_video_level = 263; +pub const VIRTIO_VIDEO_LEVEL_H264_3_1: virtio_video_level = 264; +pub const VIRTIO_VIDEO_LEVEL_H264_3_2: virtio_video_level = 265; +pub const VIRTIO_VIDEO_LEVEL_H264_4_0: virtio_video_level = 266; +pub const VIRTIO_VIDEO_LEVEL_H264_4_1: virtio_video_level = 267; +pub const VIRTIO_VIDEO_LEVEL_H264_4_2: virtio_video_level = 268; +pub const VIRTIO_VIDEO_LEVEL_H264_5_0: virtio_video_level = 269; +pub const VIRTIO_VIDEO_LEVEL_H264_5_1: virtio_video_level = 270; +pub const VIRTIO_VIDEO_LEVEL_H264_MAX: virtio_video_level = 270; +pub type virtio_video_level = u32; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_config { + pub version: Le32, + pub max_caps_length: Le32, + pub max_resp_length: Le32, +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_config {} + +pub const VIRTIO_VIDEO_CMD_QUERY_CAPABILITY: virtio_video_cmd_type = 256; +pub const VIRTIO_VIDEO_CMD_STREAM_CREATE: virtio_video_cmd_type = 257; +pub const VIRTIO_VIDEO_CMD_STREAM_DESTROY: virtio_video_cmd_type = 258; +pub const VIRTIO_VIDEO_CMD_STREAM_DRAIN: virtio_video_cmd_type = 259; +pub const VIRTIO_VIDEO_CMD_RESOURCE_CREATE: virtio_video_cmd_type = 260; +pub const VIRTIO_VIDEO_CMD_RESOURCE_QUEUE: virtio_video_cmd_type = 261; +pub const VIRTIO_VIDEO_CMD_RESOURCE_DESTROY_ALL: virtio_video_cmd_type = 262; +pub const VIRTIO_VIDEO_CMD_QUEUE_CLEAR: virtio_video_cmd_type = 263; +pub const VIRTIO_VIDEO_CMD_GET_PARAMS: virtio_video_cmd_type = 264; +pub const VIRTIO_VIDEO_CMD_SET_PARAMS: virtio_video_cmd_type = 265; +pub const VIRTIO_VIDEO_CMD_QUERY_CONTROL: virtio_video_cmd_type = 266; +pub const VIRTIO_VIDEO_CMD_GET_CONTROL: virtio_video_cmd_type = 267; +pub const VIRTIO_VIDEO_CMD_SET_CONTROL: virtio_video_cmd_type = 268; +pub const VIRTIO_VIDEO_RESP_OK_NODATA: virtio_video_cmd_type = 512; +pub const VIRTIO_VIDEO_RESP_OK_QUERY_CAPABILITY: virtio_video_cmd_type = 513; +pub const VIRTIO_VIDEO_RESP_OK_RESOURCE_QUEUE: virtio_video_cmd_type = 514; +pub const VIRTIO_VIDEO_RESP_OK_GET_PARAMS: virtio_video_cmd_type = 515; +pub const VIRTIO_VIDEO_RESP_OK_QUERY_CONTROL: virtio_video_cmd_type = 516; +pub const VIRTIO_VIDEO_RESP_OK_GET_CONTROL: virtio_video_cmd_type = 517; +pub const VIRTIO_VIDEO_RESP_ERR_INVALID_OPERATION: virtio_video_cmd_type = 768; +pub const VIRTIO_VIDEO_RESP_ERR_OUT_OF_MEMORY: virtio_video_cmd_type = 769; +pub const VIRTIO_VIDEO_RESP_ERR_INVALID_STREAM_ID: virtio_video_cmd_type = 770; +pub const VIRTIO_VIDEO_RESP_ERR_INVALID_RESOURCE_ID: virtio_video_cmd_type = 771; +pub const VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER: virtio_video_cmd_type = 772; +pub const VIRTIO_VIDEO_RESP_ERR_UNSUPPORTED_CONTROL: virtio_video_cmd_type = 773; +pub type virtio_video_cmd_type = u32; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_cmd_hdr { + pub type_: Le32, + pub stream_id: Le32, +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_cmd_hdr {} + +pub const VIRTIO_VIDEO_QUEUE_TYPE_INPUT: virtio_video_queue_type = 256; +pub const VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT: virtio_video_queue_type = 257; +pub type virtio_video_queue_type = u32; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_query_capability { + pub queue_type: Le32, + pub padding: [u8; 4usize], +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_query_capability {} + +pub const VIRTIO_VIDEO_PLANES_LAYOUT_SINGLE_BUFFER: virtio_video_planes_layout_flag = 1; +pub const VIRTIO_VIDEO_PLANES_LAYOUT_PER_PLANE: virtio_video_planes_layout_flag = 2; +pub type virtio_video_planes_layout_flag = u32; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_format_range { + pub min: Le32, + pub max: Le32, + pub step: Le32, + pub padding: [u8; 4usize], +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_format_range {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_format_frame { + pub width: virtio_video_format_range, + pub height: virtio_video_format_range, + pub num_rates: Le32, + pub padding: [u8; 4usize], +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_format_frame {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_format_desc { + pub mask: Le64, + pub format: Le32, + pub planes_layout: Le32, + pub plane_align: Le32, + pub num_frames: Le32, +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_format_desc {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_query_capability_resp { + pub hdr: virtio_video_cmd_hdr, + pub num_descs: Le32, + pub padding: [u8; 4usize], +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_query_capability_resp {} + +pub const VIRTIO_VIDEO_MEM_TYPE_GUEST_PAGES: virtio_video_mem_type = 0; +pub const VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT: virtio_video_mem_type = 1; +pub type virtio_video_mem_type = u32; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct virtio_video_stream_create { + pub in_mem_type: Le32, + pub out_mem_type: Le32, + pub coded_format: Le32, + pub padding: [u8; 4usize], + pub tag: [u8; 64usize], +} +impl Default for virtio_video_stream_create { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_stream_create {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_stream_destroy {} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_stream_destroy {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_stream_drain {} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_stream_drain {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_mem_entry { + pub addr: Le64, + pub length: Le32, + pub padding: [u8; 4usize], +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_object_entry { + pub uuid: [u8; 16usize], +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_object_entry {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_resource_create { + pub queue_type: Le32, + pub resource_id: Le32, + pub planes_layout: Le32, + pub num_planes: Le32, + pub plane_offsets: [Le32; 8usize], + pub num_entries: [Le32; 8usize], +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_resource_create {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_resource_queue { + pub queue_type: Le32, + pub resource_id: Le32, + pub timestamp: Le64, + pub num_data_sizes: Le32, + pub data_sizes: [Le32; 8usize], + pub padding: [u8; 4usize], +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_resource_queue {} + +pub const VIRTIO_VIDEO_BUFFER_FLAG_ERR: virtio_video_buffer_flag = 1; +pub const VIRTIO_VIDEO_BUFFER_FLAG_EOS: virtio_video_buffer_flag = 2; +pub const VIRTIO_VIDEO_BUFFER_FLAG_IFRAME: virtio_video_buffer_flag = 4; +pub const VIRTIO_VIDEO_BUFFER_FLAG_PFRAME: virtio_video_buffer_flag = 8; +pub const VIRTIO_VIDEO_BUFFER_FLAG_BFRAME: virtio_video_buffer_flag = 16; +pub type virtio_video_buffer_flag = u32; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_resource_queue_resp { + pub hdr: virtio_video_cmd_hdr, + pub timestamp: Le64, + pub flags: Le32, + pub size: Le32, +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_resource_queue_resp {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_resource_destroy_all { + pub queue_type: Le32, + pub padding: [u8; 4usize], +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_resource_destroy_all {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_queue_clear { + pub queue_type: Le32, + pub padding: [u8; 4usize], +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_queue_clear {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_plane_format { + pub plane_size: Le32, + pub stride: Le32, +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_crop { + pub left: Le32, + pub top: Le32, + pub width: Le32, + pub height: Le32, +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_params { + pub queue_type: Le32, + pub format: Le32, + pub frame_width: Le32, + pub frame_height: Le32, + pub min_buffers: Le32, + pub max_buffers: Le32, + pub crop: virtio_video_crop, + pub frame_rate: Le32, + pub num_planes: Le32, + pub plane_formats: [virtio_video_plane_format; 8usize], +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_get_params { + pub queue_type: Le32, + pub padding: [u8; 4usize], +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_get_params {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_get_params_resp { + pub hdr: virtio_video_cmd_hdr, + pub params: virtio_video_params, +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_get_params_resp {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_set_params { + pub params: virtio_video_params, +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_set_params {} + +pub const VIRTIO_VIDEO_CONTROL_BITRATE: virtio_video_control_type = 1; +pub const VIRTIO_VIDEO_CONTROL_PROFILE: virtio_video_control_type = 2; +pub const VIRTIO_VIDEO_CONTROL_LEVEL: virtio_video_control_type = 3; +pub type virtio_video_control_type = u32; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_query_control_profile { + pub format: Le32, + pub padding: [u8; 4usize], +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_query_control_profile {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_query_control_level { + pub format: Le32, + pub padding: [u8; 4usize], +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_query_control_level {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_query_control { + pub control: Le32, + pub padding: [u8; 4usize], +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_query_control {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_query_control_resp_profile { + pub num: Le32, + pub padding: [u8; 4usize], +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_query_control_resp_profile {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_query_control_resp_level { + pub num: Le32, + pub padding: [u8; 4usize], +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_query_control_resp_level {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_query_control_resp { + pub hdr: virtio_video_cmd_hdr, +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_query_control_resp {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_get_control { + pub control: Le32, + pub padding: [u8; 4usize], +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_get_control {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_control_val_bitrate { + pub bitrate: Le32, + pub padding: [u8; 4usize], +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_control_val_bitrate {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_control_val_profile { + pub profile: Le32, + pub padding: [u8; 4usize], +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_control_val_profile {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_control_val_level { + pub level: Le32, + pub padding: [u8; 4usize], +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_control_val_level {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_get_control_resp { + pub hdr: virtio_video_cmd_hdr, +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_get_control_resp {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_set_control { + pub control: Le32, + pub padding: [u8; 4usize], +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_set_control {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_set_control_resp { + pub hdr: virtio_video_cmd_hdr, +} +pub const VIRTIO_VIDEO_EVENT_ERROR: virtio_video_event_type = 256; +pub const VIRTIO_VIDEO_EVENT_DECODER_RESOLUTION_CHANGED: virtio_video_event_type = 512; +pub type virtio_video_event_type = u32; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct virtio_video_event { + pub event_type: Le32, + pub stream_id: Le32, +} +// Safe because auto-generated structs have no implicit padding. +unsafe impl DataInit for virtio_video_event {} diff --git a/devices/src/virtio/video/response.rs b/devices/src/virtio/video/response.rs new file mode 100644 index 0000000..1313280 --- /dev/null +++ b/devices/src/virtio/video/response.rs @@ -0,0 +1,94 @@ +// 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. + +//! Data structures for commands of virtio video devices. + +use std::io; + +use data_model::{Le32, Le64}; + +use crate::virtio::video::command::QueueType; +use crate::virtio::video::control::*; +use crate::virtio::video::format::*; +use crate::virtio::video::params::Params; +use crate::virtio::video::protocol::*; +use crate::virtio::Writer; + +pub trait Response { + /// Writes an object to virtqueue. + fn write(&self, w: &mut Writer) -> Result<(), io::Error>; +} + +/// A response to a `VideoCmd`. These correspond to `VIRTIO_VIDEO_RESP_*`. +#[derive(Debug)] +pub enum CmdResponse { + NoData, + QueryCapability(Vec<FormatDesc>), + ResourceQueue { + timestamp: u64, + flags: u32, + size: u32, + }, + GetParams { + queue_type: QueueType, + params: Params, + }, + QueryControl(QueryCtrlResponse), + GetControl(CtrlVal), +} + +impl Response for CmdResponse { + /// Writes a response to virtqueue. + fn write(&self, w: &mut Writer) -> Result<(), io::Error> { + use CmdResponse::*; + + let type_ = Le32::from(match self { + NoData => VIRTIO_VIDEO_RESP_OK_NODATA, + QueryCapability(_) => VIRTIO_VIDEO_RESP_OK_QUERY_CAPABILITY, + ResourceQueue { .. } => VIRTIO_VIDEO_RESP_OK_RESOURCE_QUEUE, + GetParams { .. } => VIRTIO_VIDEO_RESP_OK_GET_PARAMS, + QueryControl(_) => VIRTIO_VIDEO_RESP_OK_QUERY_CONTROL, + GetControl(_) => VIRTIO_VIDEO_RESP_OK_GET_CONTROL, + }); + + let hdr = virtio_video_cmd_hdr { + type_, + ..Default::default() + }; + + match self { + NoData => w.write_obj(hdr), + QueryCapability(descs) => { + w.write_obj(virtio_video_query_capability_resp { + hdr, + num_descs: Le32::from(descs.len() as u32), + ..Default::default() + })?; + descs.iter().map(|d| d.write(w)).collect() + } + ResourceQueue { + timestamp, + flags, + size, + } => w.write_obj(virtio_video_resource_queue_resp { + hdr, + timestamp: Le64::from(*timestamp), + flags: Le32::from(*flags), + size: Le32::from(*size), + }), + GetParams { queue_type, params } => { + let params = params.to_virtio_video_params(*queue_type); + w.write_obj(virtio_video_get_params_resp { hdr, params }) + } + QueryControl(r) => { + w.write_obj(virtio_video_query_control_resp { hdr })?; + r.write(w) + } + GetControl(val) => { + w.write_obj(virtio_video_get_control_resp { hdr })?; + val.write(w) + } + } + } +} diff --git a/devices/src/virtio/video/worker.rs b/devices/src/virtio/video/worker.rs new file mode 100644 index 0000000..195854f --- /dev/null +++ b/devices/src/virtio/video/worker.rs @@ -0,0 +1,362 @@ +// 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. + +//! Worker that runs in a virtio-video thread. + +use std::collections::{BTreeMap, VecDeque}; + +use sys_util::{error, EventFd, GuestMemory, PollContext}; + +use crate::virtio::queue::{DescriptorChain, Queue}; +use crate::virtio::resource_bridge::ResourceRequestSocket; +use crate::virtio::video::command::{QueueType, VideoCmd}; +use crate::virtio::video::device::{ + AsyncCmdTag, Device, Token, VideoCmdResponseType, VideoEvtResponseType, +}; +use crate::virtio::video::error::{VideoError, VideoResult}; +use crate::virtio::video::event::{self, EvtType, VideoEvt}; +use crate::virtio::video::protocol; +use crate::virtio::video::response::{self, CmdResponse, Response}; +use crate::virtio::video::{Error, Result}; +use crate::virtio::{Interrupt, Reader, Writer}; + +pub struct Worker { + pub interrupt: Interrupt, + pub mem: GuestMemory, + pub cmd_evt: EventFd, + pub event_evt: EventFd, + pub kill_evt: EventFd, + pub resource_bridge: ResourceRequestSocket, +} + +/// BTreeMap which stores descriptor chains in which asynchronous responses will be written. +type DescPool<'a> = BTreeMap<AsyncCmdTag, DescriptorChain<'a>>; +/// Pair of a descriptor chain and a response to be written. +type WritableResp<'a> = (DescriptorChain<'a>, VideoResult<response::CmdResponse>); + +/// Invalidates all pending asynchronous commands in a given `DescPool` value and returns an updated +/// `DescPool` value and a list of `WritableResp` to be sent to the guest. +fn cancel_pending_requests<'a>( + s_id: u32, + desc_pool: DescPool<'a>, +) -> (DescPool<'a>, Vec<WritableResp<'a>>) { + let mut new_desc_pool: DescPool<'a> = Default::default(); + let mut resps = vec![]; + + for (key, value) in desc_pool.into_iter() { + match key { + AsyncCmdTag::Queue { stream_id, .. } if stream_id == s_id => { + resps.push(( + value, + Ok(CmdResponse::ResourceQueue { + timestamp: 0, + flags: protocol::VIRTIO_VIDEO_BUFFER_FLAG_ERR, + size: 0, + }), + )); + } + AsyncCmdTag::Drain { stream_id } | AsyncCmdTag::Clear { stream_id, .. } + if stream_id == s_id => + { + // TODO(b/1518105): Use more appropriate error code if a new protocol supports one. + resps.push((value, Err(VideoError::InvalidOperation))); + } + AsyncCmdTag::Queue { .. } | AsyncCmdTag::Drain { .. } | AsyncCmdTag::Clear { .. } => { + // Keep commands for other streams. + new_desc_pool.insert(key, value); + } + } + } + + (new_desc_pool, resps) +} + +impl Worker { + /// Writes responses into the command queue. + fn write_responses<'a>( + &self, + cmd_queue: &mut Queue, + responses: &mut VecDeque<WritableResp>, + ) -> Result<()> { + let mut needs_interrupt_commandq = false; + // Write responses into command virtqueue. + while let Some((desc, resp)) = responses.pop_front() { + let desc_index = desc.index; + let mut writer = Writer::new(&self.mem, desc).map_err(Error::InvalidDescriptorChain)?; + match resp { + Ok(r) => { + if let Err(e) = r.write(&mut writer) { + error!("failed to write an OK response for {:?}: {}", r, e); + } + } + Err(err) => { + if let Err(e) = err.write(&mut writer) { + error!("failed to write an Error response for {:?}: {}", err, e); + } + } + } + + cmd_queue.add_used(&self.mem, desc_index, writer.bytes_written() as u32); + needs_interrupt_commandq = true; + } + if needs_interrupt_commandq { + self.interrupt.signal_used_queue(cmd_queue.vector); + } + Ok(()) + } + + /// Writes a `VideoEvt` into the event queue. + fn write_event(&self, event_queue: &mut Queue, event: &mut event::VideoEvt) -> Result<()> { + let desc = event_queue + .peek(&self.mem) + .ok_or_else(|| Error::DescriptorNotAvailable)?; + event_queue.pop_peeked(&self.mem); + + let desc_index = desc.index; + let mut writer = Writer::new(&self.mem, desc).map_err(Error::InvalidDescriptorChain)?; + event + .write(&mut writer) + .map_err(|error| Error::WriteEventFailure { + event: event.clone(), + error, + })?; + event_queue.add_used(&self.mem, desc_index, writer.bytes_written() as u32); + self.interrupt.signal_used_queue(event_queue.vector); + Ok(()) + } + + /// Handles a `DescriptorChain` value sent via the command queue and returns an updated + /// `DescPool` and `VecDeque` of `WritableResp` to be sent to the guest. + fn handle_command_desc<'a, T: Device>( + &'a self, + device: &mut T, + poll_ctx: &PollContext<Token>, + mut desc_pool: DescPool<'a>, + desc: DescriptorChain<'a>, + ) -> Result<(DescPool<'a>, VecDeque<WritableResp<'a>>)> { + let mut resps: VecDeque<WritableResp> = Default::default(); + let mut reader = + Reader::new(&self.mem, desc.clone()).map_err(Error::InvalidDescriptorChain)?; + + let cmd = VideoCmd::from_reader(&mut reader).map_err(Error::ReadFailure)?; + + // If a destruction command comes, cancel pending requests. + match cmd { + VideoCmd::ResourceDestroyAll { stream_id } | VideoCmd::StreamDestroy { stream_id } => { + let (next_desc_pool, rs) = cancel_pending_requests(stream_id, desc_pool); + desc_pool = next_desc_pool; + resps.append(&mut Into::<VecDeque<_>>::into(rs)); + } + _ => (), + }; + + // Process the command by the device. + let resp = device.process_cmd(cmd, &poll_ctx, &self.resource_bridge); + + match resp { + Ok(VideoCmdResponseType::Sync(r)) => { + resps.push_back((desc.clone(), Ok(r))); + } + Ok(VideoCmdResponseType::Async(tag)) => { + // If the command expects an asynchronous response, + // store `desc` to use it after the back-end device notifies the + // completion. + desc_pool.insert(tag, desc); + } + Err(e) => { + resps.push_back((desc.clone(), Err(e))); + } + } + + Ok((desc_pool, resps)) + } + + /// Handles the command queue returns an updated `DescPool`. + fn handle_command_queue<'a, T: Device>( + &'a self, + cmd_queue: &mut Queue, + device: &mut T, + poll_ctx: &PollContext<Token>, + mut desc_pool: DescPool<'a>, + ) -> Result<DescPool<'a>> { + let _ = self.cmd_evt.read(); + + while let Some(desc) = cmd_queue.pop(&self.mem) { + let (next_desc_pool, mut resps) = + self.handle_command_desc(device, poll_ctx, desc_pool, desc)?; + desc_pool = next_desc_pool; + self.write_responses(cmd_queue, &mut resps)?; + } + Ok(desc_pool) + } + + /// Handles a `VideoEvtResponseType` value and returns an updated `DescPool` and `VecDeque` of + /// `WritableResp` to be sent to the guest. + fn handle_event_resp<'a, T: Device>( + &'a self, + event_queue: &mut Queue, + device: &mut T, + mut desc_pool: DescPool<'a>, + resp: VideoEvtResponseType, + ) -> Result<(DescPool<'a>, VecDeque<WritableResp>)> { + let mut responses: VecDeque<WritableResp> = Default::default(); + match resp { + VideoEvtResponseType::AsyncCmd { + tag: AsyncCmdTag::Drain { stream_id }, + resp, + } => { + let tag = AsyncCmdTag::Drain { stream_id }; + let drain_desc = desc_pool + .remove(&tag) + .ok_or_else(|| Error::UnexpectedResponse(tag))?; + + // When `Drain` request is completed, returns an empty output resource + // with EOS flag first. + let resource_id = device + .take_resource_id_to_notify_eos(stream_id) + .ok_or_else(|| Error::NoEOSBuffer { stream_id })?; + + let q_desc = desc_pool + .remove(&AsyncCmdTag::Queue { + stream_id, + queue_type: QueueType::Output, + resource_id, + }) + .ok_or_else(|| Error::InvalidEOSResource { + stream_id, + resource_id, + })?; + + responses.push_back(( + q_desc, + Ok(CmdResponse::ResourceQueue { + timestamp: 0, + flags: protocol::VIRTIO_VIDEO_BUFFER_FLAG_EOS, + size: 0, + }), + )); + + // Then, responds the Drain request. + responses.push_back((drain_desc, resp)); + } + VideoEvtResponseType::AsyncCmd { + tag: + AsyncCmdTag::Clear { + queue_type, + stream_id, + }, + resp, + } => { + let tag = AsyncCmdTag::Clear { + queue_type, + stream_id, + }; + let desc = desc_pool + .remove(&tag) + .ok_or_else(|| Error::UnexpectedResponse(tag))?; + + // When `Clear` request is completed, invalidate all pending requests. + let (next_desc_pool, resps) = cancel_pending_requests(stream_id, desc_pool); + desc_pool = next_desc_pool; + responses.append(&mut Into::<VecDeque<_>>::into(resps)); + + // Then, responds the `Clear` request. + responses.push_back((desc, resp)); + } + VideoEvtResponseType::AsyncCmd { tag, resp } => { + let desc = desc_pool + .remove(&tag) + .ok_or_else(|| Error::UnexpectedResponse(tag))?; + responses.push_back((desc, resp)); + } + VideoEvtResponseType::Event(mut evt) => { + self.write_event(event_queue, &mut evt)?; + } + }; + Ok((desc_pool, responses)) + } + + /// Handles an event notified via an event FD and returns an updated `DescPool`. + fn handle_event_fd<'a, T: Device>( + &'a self, + cmd_queue: &mut Queue, + event_queue: &mut Queue, + device: &mut T, + desc_pool: DescPool<'a>, + stream_id: u32, + ) -> Result<DescPool<'a>> { + let resp = device.process_event_fd(stream_id); + match resp { + Some(r) => match self.handle_event_resp(event_queue, device, desc_pool, r) { + Ok((updated_desc_pool, mut resps)) => { + self.write_responses(cmd_queue, &mut resps)?; + Ok(updated_desc_pool) + } + Err(e) => { + // Ignore result of write_event for a fatal error. + let _ = self.write_event( + event_queue, + &mut VideoEvt { + typ: EvtType::Error, + stream_id, + }, + ); + Err(e) + } + }, + None => Ok(desc_pool), + } + } + + pub fn run<T: Device>( + &mut self, + mut cmd_queue: Queue, + mut event_queue: Queue, + mut device: T, + ) -> Result<()> { + let poll_ctx: PollContext<Token> = PollContext::build_with(&[ + (&self.cmd_evt, Token::CmdQueue), + (&self.event_evt, Token::EventQueue), + (&self.kill_evt, Token::Kill), + (self.interrupt.get_resample_evt(), Token::InterruptResample), + ]) + .map_err(Error::PollContextCreationFailed)?; + + // Stores descriptors in which responses for asynchronous commands will be written. + let mut desc_pool: DescPool<'_> = Default::default(); + + loop { + let poll_events = poll_ctx.wait().map_err(Error::PollError)?; + + for poll_event in poll_events.iter_readable() { + match poll_event.token() { + Token::CmdQueue => { + desc_pool = self.handle_command_queue( + &mut cmd_queue, + &mut device, + &poll_ctx, + desc_pool, + )?; + } + Token::EventQueue => { + let _ = self.event_evt.read(); + } + Token::EventFd { id } => { + desc_pool = self.handle_event_fd( + &mut cmd_queue, + &mut event_queue, + &mut device, + desc_pool, + id, + )?; + } + Token::InterruptResample => { + self.interrupt.interrupt_resample(); + } + Token::Kill => return Ok(()), + } + } + } + } +} diff --git a/devices/src/virtio/wl.rs b/devices/src/virtio/wl.rs index afeea3c..6fefd51 100644 --- a/devices/src/virtio/wl.rs +++ b/devices/src/virtio/wl.rs @@ -727,12 +727,10 @@ impl WlVfd { fn send(&mut self, fds: &[RawFd], data: &mut Reader) -> WlResult<WlResp> { if let Some(socket) = &self.socket { socket - .send_with_fds( - data.get_iovec(usize::max_value()) - .map_err(WlError::ParseDesc)?, - fds, - ) + .send_with_fds(data.get_remaining(), fds) .map_err(WlError::SendVfd)?; + // All remaining data in `data` is now considered consumed. + data.consume(::std::usize::MAX); Ok(WlResp::Ok) } else if let Some((_, local_pipe)) = &mut self.local_pipe { // Impossible to send fds over a simple pipe. diff --git a/disk/src/android_sparse.rs b/disk/src/android_sparse.rs index 0772bfc..6e1a50c 100644 --- a/disk/src/android_sparse.rs +++ b/disk/src/android_sparse.rs @@ -291,9 +291,9 @@ impl FileReadWriteAtVolatile for AndroidSparse { ))?; let chunk_offset = offset - chunk_start; let chunk_size = *expanded_size; - let subslice = if chunk_offset + slice.size() > chunk_size { + let subslice = if chunk_offset + (slice.size() as u64) > chunk_size { slice - .sub_slice(0, chunk_size - chunk_offset) + .sub_slice(0, (chunk_size - chunk_offset) as usize) .map_err(|e| io::Error::new(ErrorKind::InvalidData, format!("{:?}", e)))? } else { slice @@ -331,7 +331,6 @@ impl FileReadWriteAtVolatile for AndroidSparse { #[cfg(test)] mod tests { use super::*; - use data_model::VolatileMemory; use std::io::{Cursor, Write}; use sys_util::SharedMemory; @@ -435,12 +434,11 @@ mod tests { }]; let mut image = test_image(chunks); let mut input_memory = [55u8; 100]; - let input_volatile_memory = &mut input_memory[..]; image - .read_exact_at_volatile(input_volatile_memory.get_slice(0, 100).unwrap(), 0) + .read_exact_at_volatile(VolatileSlice::new(&mut input_memory[..]), 0) .expect("Could not read"); - let input_vec: Vec<u8> = input_memory.into_iter().cloned().collect(); - assert_eq!(input_vec, vec![0u8; 100]); + let expected = [0u8; 100]; + assert_eq!(&expected[..], &input_memory[..]); } #[test] @@ -451,12 +449,11 @@ mod tests { }]; let mut image = test_image(chunks); let mut input_memory = [55u8; 8]; - let input_volatile_memory = &mut input_memory[..]; image - .read_exact_at_volatile(input_volatile_memory.get_slice(0, 8).unwrap(), 0) + .read_exact_at_volatile(VolatileSlice::new(&mut input_memory[..]), 0) .expect("Could not read"); - let input_vec: Vec<u8> = input_memory.into_iter().cloned().collect(); - assert_eq!(input_vec, vec![10, 20, 10, 20, 10, 20, 10, 20]); + let expected = [10, 20, 10, 20, 10, 20, 10, 20]; + assert_eq!(&expected[..], &input_memory[..]); } #[test] @@ -467,12 +464,11 @@ mod tests { }]; let mut image = test_image(chunks); let mut input_memory = [55u8; 6]; - let input_volatile_memory = &mut input_memory[..]; image - .read_exact_at_volatile(input_volatile_memory.get_slice(0, 6).unwrap(), 1) + .read_exact_at_volatile(VolatileSlice::new(&mut input_memory[..]), 1) .expect("Could not read"); - let input_vec: Vec<u8> = input_memory.into_iter().cloned().collect(); - assert_eq!(input_vec, vec![20, 30, 10, 20, 30, 10]); + let expected = [20, 30, 10, 20, 30, 10]; + assert_eq!(&expected[..], &input_memory[..]); } #[test] @@ -489,12 +485,11 @@ mod tests { ]; let mut image = test_image(chunks); let mut input_memory = [55u8; 7]; - let input_volatile_memory = &mut input_memory[..]; image - .read_exact_at_volatile(input_volatile_memory.get_slice(0, 7).unwrap(), 39) + .read_exact_at_volatile(VolatileSlice::new(&mut input_memory[..]), 39) .expect("Could not read"); - let input_vec: Vec<u8> = input_memory.into_iter().cloned().collect(); - assert_eq!(input_vec, vec![20, 30, 10, 20, 30, 10, 20]); + let expected = [20, 30, 10, 20, 30, 10, 20]; + assert_eq!(&expected[..], &input_memory[..]); } #[test] @@ -506,12 +501,11 @@ mod tests { let mut image = test_image(chunks); write!(image.file, "hello").expect("Failed to write into internal file"); let mut input_memory = [55u8; 5]; - let input_volatile_memory = &mut input_memory[..]; image - .read_exact_at_volatile(input_volatile_memory.get_slice(0, 5).unwrap(), 0) + .read_exact_at_volatile(VolatileSlice::new(&mut input_memory[..]), 0) .expect("Could not read"); - let input_vec: Vec<u8> = input_memory.into_iter().cloned().collect(); - assert_eq!(input_vec, vec![104, 101, 108, 108, 111]); + let expected = [104, 101, 108, 108, 111]; + assert_eq!(&expected[..], &input_memory[..]); } #[test] @@ -528,11 +522,10 @@ mod tests { ]; let mut image = test_image(chunks); let mut input_memory = [55u8; 8]; - let input_volatile_memory = &mut input_memory[..]; image - .read_exact_at_volatile(input_volatile_memory.get_slice(0, 8).unwrap(), 0) + .read_exact_at_volatile(VolatileSlice::new(&mut input_memory[..]), 0) .expect("Could not read"); - let input_vec: Vec<u8> = input_memory.into_iter().cloned().collect(); - assert_eq!(input_vec, vec![10, 20, 10, 20, 30, 40, 30, 40]); + let expected = [10, 20, 10, 20, 30, 40, 30, 40]; + assert_eq!(&expected[..], &input_memory[..]); } } diff --git a/disk/src/composite.rs b/disk/src/composite.rs index e95c8e9..e316f3b 100644 --- a/disk/src/composite.rs +++ b/disk/src/composite.rs @@ -246,10 +246,10 @@ impl FileReadWriteAtVolatile for CompositeDiskFile { fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> io::Result<usize> { let cursor_location = offset; let disk = self.disk_at_offset(cursor_location)?; - let subslice = if cursor_location + slice.size() > disk.offset + disk.length { + let subslice = if cursor_location + slice.size() as u64 > disk.offset + disk.length { let new_size = disk.offset + disk.length - cursor_location; slice - .sub_slice(0, new_size) + .sub_slice(0, new_size as usize) .map_err(|e| io::Error::new(ErrorKind::InvalidData, format!("{:?}", e)))? } else { slice @@ -260,10 +260,10 @@ impl FileReadWriteAtVolatile for CompositeDiskFile { fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> io::Result<usize> { let cursor_location = offset; let disk = self.disk_at_offset(cursor_location)?; - let subslice = if cursor_location + slice.size() > disk.offset + disk.length { + let subslice = if cursor_location + slice.size() as u64 > disk.offset + disk.length { let new_size = disk.offset + disk.length - cursor_location; slice - .sub_slice(0, new_size) + .sub_slice(0, new_size as usize) .map_err(|e| io::Error::new(ErrorKind::InvalidData, format!("{:?}", e)))? } else { slice @@ -392,12 +392,12 @@ mod tests { }; let mut composite = CompositeDiskFile::new(vec![disk_part]).unwrap(); let mut input_memory = [55u8; 5]; - let input_volatile_memory = &mut input_memory[..]; + let input_volatile_memory = VolatileSlice::new(&mut input_memory[..]); composite .write_all_at_volatile(input_volatile_memory.get_slice(0, 5).unwrap(), 0) .unwrap(); let mut output_memory = [0u8; 5]; - let output_volatile_memory = &mut output_memory[..]; + let output_volatile_memory = VolatileSlice::new(&mut output_memory[..]); composite .read_exact_at_volatile(output_volatile_memory.get_slice(0, 5).unwrap(), 0) .unwrap(); @@ -455,12 +455,12 @@ mod tests { let mut composite = CompositeDiskFile::new(vec![disk_part1, disk_part2, disk_part3]).unwrap(); let mut input_memory = [55u8; 200]; - let input_volatile_memory = &mut input_memory[..]; + let input_volatile_memory = VolatileSlice::new(&mut input_memory[..]); composite .write_all_at_volatile(input_volatile_memory.get_slice(0, 200).unwrap(), 50) .unwrap(); let mut output_memory = [0u8; 200]; - let output_volatile_memory = &mut output_memory[..]; + let output_volatile_memory = VolatileSlice::new(&mut output_memory[..]); composite .read_exact_at_volatile(output_volatile_memory.get_slice(0, 200).unwrap(), 50) .unwrap(); @@ -490,13 +490,13 @@ mod tests { let mut composite = CompositeDiskFile::new(vec![disk_part1, disk_part2, disk_part3]).unwrap(); let mut input_memory = [55u8; 300]; - let input_volatile_memory = &mut input_memory[..]; + let input_volatile_memory = VolatileSlice::new(&mut input_memory[..]); composite .write_all_at_volatile(input_volatile_memory.get_slice(0, 300).unwrap(), 0) .unwrap(); composite.punch_hole(50, 200).unwrap(); let mut output_memory = [0u8; 300]; - let output_volatile_memory = &mut output_memory[..]; + let output_volatile_memory = VolatileSlice::new(&mut output_memory[..]); composite .read_exact_at_volatile(output_volatile_memory.get_slice(0, 300).unwrap(), 0) .unwrap(); @@ -530,7 +530,7 @@ mod tests { let mut composite = CompositeDiskFile::new(vec![disk_part1, disk_part2, disk_part3]).unwrap(); let mut input_memory = [55u8; 300]; - let input_volatile_memory = &mut input_memory[..]; + let input_volatile_memory = VolatileSlice::new(&mut input_memory[..]); composite .write_all_at_volatile(input_volatile_memory.get_slice(0, 300).unwrap(), 0) .unwrap(); @@ -541,7 +541,7 @@ mod tests { .unwrap(); } let mut output_memory = [0u8; 300]; - let output_volatile_memory = &mut output_memory[..]; + let output_volatile_memory = VolatileSlice::new(&mut output_memory[..]); composite .read_exact_at_volatile(output_volatile_memory.get_slice(0, 300).unwrap(), 0) .unwrap(); diff --git a/disk/src/qcow/mod.rs b/disk/src/qcow/mod.rs index c5e119d..c699e50 100644 --- a/disk/src/qcow/mod.rs +++ b/disk/src/qcow/mod.rs @@ -1090,8 +1090,7 @@ impl QcowFile { let cluster_size = self.raw_file.cluster_size(); let cluster_begin = address - (address % cluster_size); let mut cluster_data = vec![0u8; cluster_size as usize]; - let raw_slice = cluster_data.as_mut_slice(); - let volatile_slice = raw_slice.get_slice(0, cluster_size).unwrap(); + let volatile_slice = VolatileSlice::new(&mut cluster_data); backing.read_exact_at_volatile(volatile_slice, cluster_begin)?; Some(cluster_data) } else { @@ -1537,12 +1536,13 @@ impl AsRawFds for QcowFile { impl Read for QcowFile { fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { - let slice = buf.get_slice(0, buf.len() as u64).unwrap(); + let len = buf.len(); + let slice = VolatileSlice::new(buf); let read_count = self.read_cb( self.current_offset, - buf.len(), + len, |file, already_read, offset, count| { - let sub_slice = slice.get_slice(already_read as u64, count as u64).unwrap(); + let sub_slice = slice.get_slice(already_read, count).unwrap(); match file { Some(f) => f.read_exact_at_volatile(sub_slice, offset), None => { @@ -1610,9 +1610,9 @@ impl FileReadWriteVolatile for QcowFile { fn read_volatile(&mut self, slice: VolatileSlice) -> io::Result<usize> { let read_count = self.read_cb( self.current_offset, - slice.size() as usize, + slice.size(), |file, read, offset, count| { - let sub_slice = slice.get_slice(read as u64, count as u64).unwrap(); + let sub_slice = slice.get_slice(read, count).unwrap(); match file { Some(f) => f.read_exact_at_volatile(sub_slice, offset), None => { @@ -1627,14 +1627,11 @@ impl FileReadWriteVolatile for QcowFile { } fn write_volatile(&mut self, slice: VolatileSlice) -> io::Result<usize> { - let write_count = self.write_cb( - self.current_offset, - slice.size() as usize, - |file, offset, count| { - let sub_slice = slice.get_slice(offset as u64, count as u64).unwrap(); + let write_count = + self.write_cb(self.current_offset, slice.size(), |file, offset, count| { + let sub_slice = slice.get_slice(offset, count).unwrap(); file.write_all_volatile(sub_slice) - }, - )?; + })?; self.current_offset += write_count as u64; Ok(write_count) } @@ -1642,25 +1639,21 @@ impl FileReadWriteVolatile for QcowFile { impl FileReadWriteAtVolatile for QcowFile { fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> io::Result<usize> { - self.read_cb( - offset, - slice.size() as usize, - |file, read, offset, count| { - let sub_slice = slice.get_slice(read as u64, count as u64).unwrap(); - match file { - Some(f) => f.read_exact_at_volatile(sub_slice, offset), - None => { - sub_slice.write_bytes(0); - Ok(()) - } + self.read_cb(offset, slice.size(), |file, read, offset, count| { + let sub_slice = slice.get_slice(read, count).unwrap(); + match file { + Some(f) => f.read_exact_at_volatile(sub_slice, offset), + None => { + sub_slice.write_bytes(0); + Ok(()) } - }, - ) + } + }) } fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> io::Result<usize> { - self.write_cb(offset, slice.size() as usize, |file, offset, count| { - let sub_slice = slice.get_slice(offset as u64, count as u64).unwrap(); + self.write_cb(offset, slice.size(), |file, offset, count| { + let sub_slice = slice.get_slice(offset, count).unwrap(); file.write_all_volatile(sub_slice) }) } diff --git a/disk/src/qcow/qcow_raw_file.rs b/disk/src/qcow/qcow_raw_file.rs index 09d2176..5ffdc30 100644 --- a/disk/src/qcow/qcow_raw_file.rs +++ b/disk/src/qcow/qcow_raw_file.rs @@ -6,7 +6,7 @@ use std::fs::File; use std::io::{self, BufWriter, Read, Seek, SeekFrom, Write}; use std::mem::size_of; -use data_model::VolatileMemory; +use data_model::VolatileSlice; use sys_util::{FileReadWriteAtVolatile, WriteZeroes}; /// A qcow file. Allows reading/writing clusters and appending clusters. @@ -149,10 +149,13 @@ impl QcowRawFile { /// Writes pub fn write_cluster(&mut self, address: u64, mut initial_data: Vec<u8>) -> io::Result<()> { - let raw_slice = initial_data.as_mut_slice(); - let volatile_slice = raw_slice - .get_slice(0, self.cluster_size) - .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{:?}", e)))?; + if (initial_data.len() as u64) < self.cluster_size { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "`initial_data` is too small", + )); + } + let volatile_slice = VolatileSlice::new(&mut initial_data[..self.cluster_size as usize]); self.file.write_all_at_volatile(volatile_slice, address) } } diff --git a/docker/checkout_commits.env b/docker/checkout_commits.env index 712b5f3..67ec77b 100644 --- a/docker/checkout_commits.env +++ b/docker/checkout_commits.env @@ -2,5 +2,5 @@ MESON_COMMIT=a1a8772034aef90e8d58230d8bcfce54ab27bf6a LIBEPOXY_COMMIT=af38a466caf9c2ae49b8acda4ff842ae44d57f78 TPM2_COMMIT=a9bc45bb7fafc65ea8a787894434d409f533b1f1 PLATFORM2_COMMIT=fabad43f1182bf71b3771735b4488180d08f3d59 -ADHD_COMMIT=8405d713c2c293646723a424c218af4a72e598f2 +ADHD_COMMIT=e64841b1bba00f6e2c113b002f6d1e98bc904b0d DRM_COMMIT=00320d7d68ddc7d815d073bb7c92d9a1f9bb8c31 diff --git a/fuzz/virtqueue_fuzzer.rs b/fuzz/virtqueue_fuzzer.rs index dc746d4..70ad330 100644 --- a/fuzz/virtqueue_fuzzer.rs +++ b/fuzz/virtqueue_fuzzer.rs @@ -8,7 +8,6 @@ use std::mem::size_of; use cros_fuzz::fuzz_target; use cros_fuzz::rand::FuzzRng; -use data_model::VolatileMemory; use devices::virtio::{DescriptorChain, Queue}; use rand::{Rng, RngCore}; use sys_util::{GuestAddress, GuestMemory}; @@ -71,7 +70,9 @@ fuzz_target!(|data: &[u8]| { } // First zero out all of the memory. - let vs = mem.get_slice(0, MEM_SIZE).unwrap(); + let vs = mem + .get_slice_at_addr(GuestAddress(0), MEM_SIZE as usize) + .unwrap(); vs.write_bytes(0); // Fill in the descriptor table. diff --git a/gpu_buffer/src/lib.rs b/gpu_buffer/src/lib.rs index 5d25fa2..4b56f19 100644 --- a/gpu_buffer/src/lib.rs +++ b/gpu_buffer/src/lib.rs @@ -461,7 +461,6 @@ impl AsRawFd for Buffer { #[cfg(test)] mod tests { use super::*; - use data_model::VolatileMemory; use std::fmt::Write; #[test] diff --git a/gpu_display/examples/simple_open.rs b/gpu_display/examples/simple_open.rs index bb7b1a2..f1b5721 100644 --- a/gpu_display/examples/simple_open.rs +++ b/gpu_display/examples/simple_open.rs @@ -13,7 +13,7 @@ fn main() { row[x] = b | (g << 8); } mem.as_volatile_slice() - .offset((1280 * 4 * y) as u64) + .offset(1280 * 4 * y) .unwrap() .copy_from(&row); } diff --git a/gpu_display/src/gpu_display_stub.rs b/gpu_display/src/gpu_display_stub.rs index 605e009..3089f1f 100644 --- a/gpu_display/src/gpu_display_stub.rs +++ b/gpu_display/src/gpu_display_stub.rs @@ -18,7 +18,6 @@ struct Buffer { width: u32, height: u32, bytes_per_pixel: u32, - bytes_total: u64, bytes: Vec<u8>, } @@ -28,7 +27,7 @@ impl Drop for Buffer { impl Buffer { fn as_volatile_slice(&mut self) -> VolatileSlice { - unsafe { VolatileSlice::new(self.bytes.as_mut_ptr(), self.bytes_total) } + VolatileSlice::new(self.bytes.as_mut_slice()) } fn stride(&self) -> usize { @@ -66,7 +65,6 @@ impl Surface { width: self.width, height: self.height, bytes_per_pixel, - bytes_total, bytes: vec![0; bytes_total as usize], }); } diff --git a/gpu_display/src/gpu_display_wl.rs b/gpu_display/src/gpu_display_wl.rs index b96b8ef..9a3a969 100644 --- a/gpu_display/src/gpu_display_wl.rs +++ b/gpu_display/src/gpu_display_wl.rs @@ -255,10 +255,7 @@ impl DisplayT for DisplayWl { let buffer_index = (surface.buffer_index.get() + 1) % BUFFER_COUNT; let framebuffer = surface .buffer_mem - .get_slice( - (buffer_index * surface.buffer_size) as u64, - surface.buffer_size as u64, - ) + .get_slice(buffer_index * surface.buffer_size, surface.buffer_size) .ok()?; Some(GpuDisplayFramebuffer::new( framebuffer, diff --git a/gpu_display/src/gpu_display_x.rs b/gpu_display/src/gpu_display_x.rs index 0882dc9..223fe08 100644 --- a/gpu_display/src/gpu_display_x.rs +++ b/gpu_display/src/gpu_display_x.rs @@ -202,7 +202,7 @@ impl Drop for Buffer { impl Buffer { fn as_volatile_slice(&self) -> VolatileSlice { - unsafe { VolatileSlice::new(self.segment_info.shmaddr as *mut _, self.size as u64) } + unsafe { VolatileSlice::from_raw_parts(self.segment_info.shmaddr as *mut _, self.size) } } fn stride(&self) -> usize { diff --git a/gpu_display/src/lib.rs b/gpu_display/src/lib.rs index dccf1c7..5b1cf94 100644 --- a/gpu_display/src/lib.rs +++ b/gpu_display/src/lib.rs @@ -107,7 +107,7 @@ impl<'a> GpuDisplayFramebuffer<'a> { .checked_add(width_bytes)?; let slice = self .framebuffer - .sub_slice(byte_offset as u64, count as u64) + .sub_slice(byte_offset as usize, count as usize) .unwrap(); Some(GpuDisplayFramebuffer { slice, ..*self }) diff --git a/gpu_renderer/src/lib.rs b/gpu_renderer/src/lib.rs index 5e5ab95..9cdb5a3 100644 --- a/gpu_renderer/src/lib.rs +++ b/gpu_renderer/src/lib.rs @@ -13,7 +13,6 @@ use std::cell::RefCell; use std::ffi::CString; use std::fmt::{self, Display}; use std::fs::File; -use std::marker::PhantomData; use std::mem::{size_of, transmute}; use std::os::raw::{c_char, c_void}; use std::os::unix::io::FromRawFd; @@ -24,7 +23,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use libc::close; -use data_model::{VolatileMemory, VolatileSlice}; +use data_model::VolatileSlice; use sys_util::{debug, GuestAddress, GuestMemory}; use crate::generated::p_defines::{ @@ -222,7 +221,6 @@ impl From<RendererFlags> for i32 { /// The global renderer handle used to query capability sets, and create resources and contexts. pub struct Renderer { - no_sync_send: PhantomData<*mut ()>, fence_state: Rc<RefCell<FenceState>>, } @@ -266,10 +264,7 @@ impl Renderer { }; ret_to_res(ret)?; - Ok(Renderer { - no_sync_send: PhantomData, - fence_state, - }) + Ok(Renderer { fence_state }) } /// Gets the version and size for the given capability set ID. @@ -309,10 +304,7 @@ impl Renderer { ) }; ret_to_res(ret)?; - Ok(Context { - id, - no_sync_send: PhantomData, - }) + Ok(Context { id }) } /// Creates a resource with the given arguments. @@ -328,7 +320,6 @@ impl Renderer { id: args.handle, backing_iovecs: Vec::new(), backing_mem: None, - no_sync_send: PhantomData, }) } @@ -420,7 +411,7 @@ impl Renderer { { if vecs .iter() - .any(|&(addr, len)| mem.get_slice(addr.offset(), len as u64).is_err()) + .any(|&(addr, len)| mem.get_slice_at_addr(addr, len).is_err()) { return Err(Error::InvalidIovec); } @@ -428,9 +419,9 @@ impl Renderer { let mut iovecs = Vec::new(); for &(addr, len) in vecs { // Unwrap will not panic because we already checked the slices. - let slice = mem.get_slice(addr.offset(), len as u64).unwrap(); + let slice = mem.get_slice_at_addr(addr, len).unwrap(); iovecs.push(VirglVec { - base: slice.as_ptr() as *mut c_void, + base: slice.as_mut_ptr() as *mut c_void, len, }); } @@ -453,7 +444,6 @@ impl Renderer { id: resource_id, backing_iovecs: iovecs, backing_mem: None, - no_sync_send: PhantomData, }) } #[cfg(not(feature = "virtio-gpu-next"))] @@ -486,7 +476,6 @@ impl Renderer { /// A context in which resources can be attached/detached and commands can be submitted. pub struct Context { id: u32, - no_sync_send: PhantomData<*mut ()>, } impl Context { @@ -543,7 +532,6 @@ pub struct Resource { id: u32, backing_iovecs: Vec<VirglVec>, backing_mem: Option<GuestMemory>, - no_sync_send: PhantomData<*mut ()>, } impl Resource { @@ -603,7 +591,7 @@ impl Resource { ) -> Result<()> { if iovecs .iter() - .any(|&(addr, len)| mem.get_slice(addr.offset(), len as u64).is_err()) + .any(|&(addr, len)| mem.get_slice_at_addr(addr, len).is_err()) { return Err(Error::InvalidIovec); } @@ -611,9 +599,9 @@ impl Resource { self.backing_mem = Some(mem.clone()); for &(addr, len) in iovecs { // Unwrap will not panic because we already checked the slices. - let slice = mem.get_slice(addr.offset(), len as u64).unwrap(); + let slice = mem.get_slice_at_addr(addr, len).unwrap(); self.backing_iovecs.push(VirglVec { - base: slice.as_ptr() as *mut c_void, + base: slice.as_mut_ptr() as *mut c_void, len, }); } diff --git a/hypervisor/Cargo.toml b/hypervisor/Cargo.toml index 8f07d5d..db3a095 100644 --- a/hypervisor/Cargo.toml +++ b/hypervisor/Cargo.toml @@ -5,6 +5,8 @@ authors = ["The Chromium OS Authors"] edition = "2018" [dependencies] +bit_field = { path = "../bit_field" } +enumn = { path = "../enumn" } libc = "*" kvm = { path = "../kvm" } kvm_sys = { path = "../kvm_sys" } diff --git a/hypervisor/src/kvm/x86_64.rs b/hypervisor/src/kvm/x86_64.rs index 43d6c97..06774f4 100644 --- a/hypervisor/src/kvm/x86_64.rs +++ b/hypervisor/src/kvm/x86_64.rs @@ -2,12 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +use std::convert::TryInto; + use kvm_sys::*; use libc::E2BIG; use sys_util::{ioctl_with_mut_ptr, Error, Result}; use super::{Kvm, KvmVcpu, KvmVm}; -use crate::{CpuId, CpuIdEntry, HypervisorX86_64, Regs, VcpuX86_64, VmX86_64}; +use crate::{ + CpuId, CpuIdEntry, HypervisorX86_64, IoapicRedirectionTableEntry, IoapicState, LapicState, + PicState, PitChannelState, PitState, Regs, VcpuX86_64, VmX86_64, +}; type KvmCpuId = kvm::CpuId; @@ -93,11 +98,222 @@ impl VcpuX86_64 for KvmVcpu { } } +impl From<&kvm_pic_state> for PicState { + fn from(item: &kvm_pic_state) -> Self { + PicState { + last_irr: item.last_irr, + irr: item.irr, + imr: item.imr, + isr: item.isr, + priority_add: item.priority_add, + irq_base: item.irq_base, + read_reg_select: item.read_reg_select != 0, + poll: item.poll != 0, + special_mask: item.special_mask != 0, + init_state: item.init_state.into(), + auto_eoi: item.auto_eoi != 0, + rotate_on_auto_eoi: item.rotate_on_auto_eoi != 0, + special_fully_nested_mode: item.special_fully_nested_mode != 0, + use_4_byte_icw: item.init4 != 0, + elcr: item.elcr, + elcr_mask: item.elcr_mask, + } + } +} + +impl From<&PicState> for kvm_pic_state { + fn from(item: &PicState) -> Self { + kvm_pic_state { + last_irr: item.last_irr, + irr: item.irr, + imr: item.imr, + isr: item.isr, + priority_add: item.priority_add, + irq_base: item.irq_base, + read_reg_select: item.read_reg_select as u8, + poll: item.poll as u8, + special_mask: item.special_mask as u8, + init_state: item.init_state as u8, + auto_eoi: item.auto_eoi as u8, + rotate_on_auto_eoi: item.rotate_on_auto_eoi as u8, + special_fully_nested_mode: item.special_fully_nested_mode as u8, + init4: item.use_4_byte_icw as u8, + elcr: item.elcr, + elcr_mask: item.elcr_mask, + } + } +} + +impl From<&kvm_ioapic_state> for IoapicState { + fn from(item: &kvm_ioapic_state) -> Self { + let mut state = IoapicState { + base_address: item.base_address, + ioregsel: item.ioregsel, + ioapicid: item.id, + current_interrupt_level_bitmap: item.irr, + redirect_table: [IoapicRedirectionTableEntry::default(); 24], + }; + for (in_state, out_state) in item.redirtbl.iter().zip(state.redirect_table.iter_mut()) { + *out_state = in_state.into(); + } + state + } +} + +impl From<&IoapicRedirectionTableEntry> for kvm_ioapic_state__bindgen_ty_1 { + fn from(item: &IoapicRedirectionTableEntry) -> Self { + kvm_ioapic_state__bindgen_ty_1 { + // IoapicRedirectionTableEntry layout matches the exact bit layout of a hardware + // ioapic redirection table entry, so we can simply do a 64-bit copy + bits: item.get(0, 64), + } + } +} + +impl From<&kvm_ioapic_state__bindgen_ty_1> for IoapicRedirectionTableEntry { + fn from(item: &kvm_ioapic_state__bindgen_ty_1) -> Self { + let mut entry = IoapicRedirectionTableEntry::default(); + // Safe because the 64-bit layout of the IoapicRedirectionTableEntry matches the kvm_sys + // table entry layout + entry.set(0, 64, unsafe { item.bits as u64 }); + entry + } +} + +impl From<&IoapicState> for kvm_ioapic_state { + fn from(item: &IoapicState) -> Self { + let mut state = kvm_ioapic_state { + base_address: item.base_address, + ioregsel: item.ioregsel, + id: item.ioapicid, + irr: item.current_interrupt_level_bitmap, + ..Default::default() + }; + for (in_state, out_state) in item.redirect_table.iter().zip(state.redirtbl.iter_mut()) { + *out_state = in_state.into(); + } + state + } +} + +impl From<&LapicState> for kvm_lapic_state { + fn from(item: &LapicState) -> Self { + let mut state = kvm_lapic_state::default(); + // There are 64 lapic registers + for (reg, value) in item.regs.iter().enumerate() { + // Each lapic register is 16 bytes, but only the first 4 are used + let reg_offset = 16 * reg; + let sliceu8 = unsafe { + // This array is only accessed as parts of a u32 word, so interpret it as a u8 array. + // to_le_bytes() produces an array of u8, not i8(c_char). + std::mem::transmute::<&mut [i8], &mut [u8]>( + &mut state.regs[reg_offset..reg_offset + 4], + ) + }; + sliceu8.copy_from_slice(&value.to_le_bytes()); + } + state + } +} + +impl From<&kvm_lapic_state> for LapicState { + fn from(item: &kvm_lapic_state) -> Self { + let mut state = LapicState { regs: [0; 64] }; + // There are 64 lapic registers + for reg in 0..64 { + // Each lapic register is 16 bytes, but only the first 4 are used + let reg_offset = 16 * reg; + let bytes = unsafe { + // This array is only accessed as parts of a u32 word, so interpret it as a u8 array. + // from_le_bytes() only works on arrays of u8, not i8(c_char). + std::mem::transmute::<&[i8], &[u8]>(&item.regs[reg_offset..reg_offset + 4]) + }; + state.regs[reg] = u32::from_le_bytes(bytes.try_into().unwrap()); + } + state + } +} + +impl From<&PitState> for kvm_pit_state2 { + fn from(item: &PitState) -> Self { + kvm_pit_state2 { + channels: [ + kvm_pit_channel_state::from(&item.channels[0]), + kvm_pit_channel_state::from(&item.channels[1]), + kvm_pit_channel_state::from(&item.channels[2]), + ], + flags: item.flags, + ..Default::default() + } + } +} + +impl From<&kvm_pit_state2> for PitState { + fn from(item: &kvm_pit_state2) -> Self { + PitState { + channels: [ + PitChannelState::from(&item.channels[0]), + PitChannelState::from(&item.channels[1]), + PitChannelState::from(&item.channels[2]), + ], + flags: item.flags, + } + } +} + +impl From<&PitChannelState> for kvm_pit_channel_state { + fn from(item: &PitChannelState) -> Self { + kvm_pit_channel_state { + count: item.count, + latched_count: item.latched_count, + count_latched: item.count_latched as u8, + status_latched: item.status_latched as u8, + status: item.status, + read_state: item.read_state as u8, + write_state: item.write_state as u8, + // kvm's write_latch only stores the low byte of the reload value + write_latch: item.reload_value as u8, + rw_mode: item.rw_mode as u8, + mode: item.mode, + bcd: item.bcd as u8, + gate: item.gate as u8, + count_load_time: item.count_load_time as i64, + } + } +} + +impl From<&kvm_pit_channel_state> for PitChannelState { + fn from(item: &kvm_pit_channel_state) -> Self { + PitChannelState { + count: item.count, + latched_count: item.latched_count, + count_latched: item.count_latched.into(), + status_latched: item.status_latched != 0, + status: item.status, + read_state: item.read_state.into(), + write_state: item.write_state.into(), + // kvm's write_latch only stores the low byte of the reload value + reload_value: item.write_latch as u16, + rw_mode: item.rw_mode.into(), + mode: item.mode, + bcd: item.bcd != 0, + gate: item.gate != 0, + count_load_time: item.count_load_time as u64, + } + } +} + #[cfg(test)] mod tests { + use crate::{ + DeliveryMode, DeliveryStatus, DestinationMode, IoapicRedirectionTableEntry, IoapicState, + LapicState, PicInitState, PicState, PitChannelState, PitRWMode, PitRWState, PitState, + TriggerMode, + }; + use kvm_sys::*; + use super::Kvm; use crate::HypervisorX86_64; - use kvm_sys::*; #[test] fn get_supported_cpuid() { @@ -121,4 +337,168 @@ mod tests { .unwrap(); assert!(cpuid.cpu_id_entries.len() > 4); } + + #[test] + fn pic_state() { + let state = PicState { + last_irr: 0b00000001, + irr: 0b00000010, + imr: 0b00000100, + isr: 0b00001000, + priority_add: 0b00010000, + irq_base: 0b00100000, + read_reg_select: false, + poll: true, + special_mask: true, + init_state: PicInitState::Icw3, + auto_eoi: true, + rotate_on_auto_eoi: false, + special_fully_nested_mode: true, + use_4_byte_icw: true, + elcr: 0b01000000, + elcr_mask: 0b10000000, + }; + + let kvm_state = kvm_pic_state::from(&state); + + assert_eq!(kvm_state.last_irr, 0b00000001); + assert_eq!(kvm_state.irr, 0b00000010); + assert_eq!(kvm_state.imr, 0b00000100); + assert_eq!(kvm_state.isr, 0b00001000); + assert_eq!(kvm_state.priority_add, 0b00010000); + assert_eq!(kvm_state.irq_base, 0b00100000); + assert_eq!(kvm_state.read_reg_select, 0); + assert_eq!(kvm_state.poll, 1); + assert_eq!(kvm_state.special_mask, 1); + assert_eq!(kvm_state.init_state, 0b10); + assert_eq!(kvm_state.auto_eoi, 1); + assert_eq!(kvm_state.rotate_on_auto_eoi, 0); + assert_eq!(kvm_state.special_fully_nested_mode, 1); + assert_eq!(kvm_state.auto_eoi, 1); + assert_eq!(kvm_state.elcr, 0b01000000); + assert_eq!(kvm_state.elcr_mask, 0b10000000); + + let orig_state = PicState::from(&kvm_state); + assert_eq!(state, orig_state); + } + + #[test] + fn ioapic_state() { + let mut entry = IoapicRedirectionTableEntry::default(); + // default entry should be 0 + assert_eq!(entry.get(0, 64), 0); + + // set some values on our entry + entry.set_vector(0b11111111); + entry.set_delivery_mode(DeliveryMode::SMI); + entry.set_dest_mode(DestinationMode::Physical); + entry.set_delivery_status(DeliveryStatus::Pending); + entry.set_polarity(1); + entry.set_remote_irr(true); + entry.set_trigger_mode(TriggerMode::Level); + entry.set_interrupt_mask(true); + entry.set_dest_id(0b10101010); + + // Bit repr as: destid-reserved--------------------------------flags----vector-- + let bit_repr = 0b1010101000000000000000000000000000000000000000011111001011111111; + // where flags is [interrupt_mask(1), trigger_mode(Level=1), remote_irr(1), polarity(1), + // delivery_status(Pending=1), dest_mode(Physical=0), delivery_mode(SMI=010)] + + assert_eq!(entry.get(0, 64), bit_repr); + + let state = IoapicState { + base_address: 1, + ioregsel: 2, + ioapicid: 4, + current_interrupt_level_bitmap: 8, + redirect_table: [entry; 24], + }; + + let kvm_state = kvm_ioapic_state::from(&state); + assert_eq!(kvm_state.base_address, 1); + assert_eq!(kvm_state.ioregsel, 2); + assert_eq!(kvm_state.id, 4); + assert_eq!(kvm_state.irr, 8); + assert_eq!(kvm_state.pad, 0); + // check our entries + for i in 0..24 { + assert_eq!(unsafe { kvm_state.redirtbl[i].bits }, bit_repr); + } + + // compare with a conversion back + assert_eq!(state, IoapicState::from(&kvm_state)); + } + + #[test] + fn lapic_state() { + let mut state = LapicState { regs: [0; 64] }; + // Apic id register, 4 bytes each with a different bit set + state.regs[2] = 1 | 2 << 8 | 4 << 16 | 8 << 24; + + let kvm_state = kvm_lapic_state::from(&state); + + // check little endian bytes in kvm_state + for i in 0..4 { + assert_eq!( + unsafe { std::mem::transmute::<i8, u8>(kvm_state.regs[32 + i]) } as u8, + 2u8.pow(i as u32) + ); + } + + // Test converting back to a LapicState + assert_eq!(state, LapicState::from(&kvm_state)); + } + + #[test] + fn pit_state() { + let channel = PitChannelState { + count: 256, + latched_count: 512, + count_latched: PitRWState::LSB, + status_latched: false, + status: 7, + read_state: PitRWState::MSB, + write_state: PitRWState::Word1, + reload_value: 8, + rw_mode: PitRWMode::Both, + mode: 5, + bcd: false, + gate: true, + count_load_time: 1024, + }; + + let kvm_channel = kvm_pit_channel_state::from(&channel); + + // compare the various field translations + assert_eq!(kvm_channel.count, 256); + assert_eq!(kvm_channel.latched_count, 512); + assert_eq!(kvm_channel.count_latched, 1); + assert_eq!(kvm_channel.status_latched, 0); + assert_eq!(kvm_channel.status, 7); + assert_eq!(kvm_channel.read_state, 2); + assert_eq!(kvm_channel.write_state, 4); + assert_eq!(kvm_channel.write_latch, 8); + assert_eq!(kvm_channel.rw_mode, 3); + assert_eq!(kvm_channel.mode, 5); + assert_eq!(kvm_channel.bcd, 0); + assert_eq!(kvm_channel.gate, 1); + assert_eq!(kvm_channel.count_load_time, 1024); + + // convert back and compare + assert_eq!(channel, PitChannelState::from(&kvm_channel)); + + // convert the full pitstate + let state = PitState { + channels: [channel, channel, channel], + flags: 255, + }; + let kvm_state = kvm_pit_state2::from(&state); + + assert_eq!(kvm_state.flags, 255); + + // compare a channel + assert_eq!(channel, PitChannelState::from(&kvm_state.channels[0])); + // convert back and compare + assert_eq!(state, PitState::from(&kvm_state)); + } } diff --git a/hypervisor/src/x86_64.rs b/hypervisor/src/x86_64.rs index 87f0777..c311924 100644 --- a/hypervisor/src/x86_64.rs +++ b/hypervisor/src/x86_64.rs @@ -2,7 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use sys_util::Result; +use bit_field::*; +use sys_util::{error, Result}; use crate::{Hypervisor, Vcpu, Vm}; @@ -34,6 +35,8 @@ pub trait VcpuX86_64: Vcpu { /// about the hypervisor or vm. Information is returned in the eax, ebx, ecx and edx registers /// by the cpu for a given function and index/subfunction (passed into the cpu via the eax and ecx /// register respectively). +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct CpuIdEntry { pub function: u32, pub index: u32, @@ -50,3 +53,292 @@ pub struct CpuId { /// The state of a vcpu's general-purpose registers. pub struct Regs {} + +#[bitfield] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum DestinationMode { + Physical = 0, + Logical = 1, +} + +#[bitfield] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum TriggerMode { + Edge = 0, + Level = 1, +} + +#[bitfield] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DeliveryMode { + Fixed = 0b000, + Lowest = 0b001, + SMI = 0b010, // System management interrupt + RemoteRead = 0b011, // This is no longer supported by intel. + NMI = 0b100, // Non maskable interrupt + Init = 0b101, + Startup = 0b110, + External = 0b111, +} + +#[bitfield] +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct MsiAddressMessage { + pub reserved: BitField2, + #[bits = 1] + pub destination_mode: DestinationMode, + pub redirection_hint: BitField1, + pub reserved_2: BitField8, + pub destination_id: BitField8, + // According to Intel's implementation of MSI, these bits must always be 0xfee. + pub always_0xfee: BitField12, +} + +#[bitfield] +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct MsiDataMessage { + pub vector: BitField8, + #[bits = 3] + pub delivery_mode: DeliveryMode, + pub reserved: BitField3, + pub level: BitField1, + #[bits = 1] + pub trigger: TriggerMode, + pub reserved2: BitField16, +} + +#[bitfield] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DeliveryStatus { + Idle = 0, + Pending = 1, +} + +/// Represents a IOAPIC redirection table entry. +#[bitfield] +#[derive(Clone, Copy, Default, PartialEq, Eq)] +pub struct IoapicRedirectionTableEntry { + vector: BitField8, + #[bits = 3] + delivery_mode: DeliveryMode, + #[bits = 1] + dest_mode: DestinationMode, + #[bits = 1] + delivery_status: DeliveryStatus, + polarity: BitField1, + remote_irr: bool, + #[bits = 1] + trigger_mode: TriggerMode, + interrupt_mask: bool, // true iff interrupts are masked. + reserved: BitField39, + dest_id: BitField8, +} + +/// Number of pins on the IOAPIC. +pub const NUM_IOAPIC_PINS: usize = 24; + +/// Represents the state of the IOAPIC. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct IoapicState { + /// base_address is the memory base address for this IOAPIC. It cannot be changed. + pub base_address: u64, + /// ioregsel register. Used for selecting which entry of the redirect table to read/write. + pub ioregsel: u32, + /// ioapicid register. Bits 24 - 27 contain the APIC ID for this device. + pub ioapicid: u32, + /// current_interrupt_level_bitmap represents a bitmap of the state of all of the irq lines + pub current_interrupt_level_bitmap: u32, + /// redirect_table contains the irq settings for each irq line + pub redirect_table: [IoapicRedirectionTableEntry; 24], +} + +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PicSelect { + Primary = 0, + Secondary = 1, +} + +#[repr(C)] +#[derive(enumn::N, Debug, Clone, Copy, PartialEq, Eq)] +pub enum PicInitState { + Icw1 = 0, + Icw2 = 1, + Icw3 = 2, + Icw4 = 3, +} + +/// Convenience implementation for converting from a u8 +impl From<u8> for PicInitState { + fn from(item: u8) -> Self { + PicInitState::n(item).unwrap_or_else(|| { + error!("Invalid PicInitState {}, setting to 0", item); + PicInitState::Icw1 + }) + } +} + +/// Represents the state of the PIC. +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PicState { + /// Edge detection. + pub last_irr: u8, + /// Interrupt Request Register. + pub irr: u8, + /// Interrupt Mask Register. + pub imr: u8, + /// Interrupt Service Register. + pub isr: u8, + /// Highest priority, for priority rotation. + pub priority_add: u8, + pub irq_base: u8, + pub read_reg_select: bool, + pub poll: bool, + pub special_mask: bool, + pub init_state: PicInitState, + pub auto_eoi: bool, + pub rotate_on_auto_eoi: bool, + pub special_fully_nested_mode: bool, + /// PIC takes either 3 or 4 bytes of initialization command word during + /// initialization. use_4_byte_icw is true if 4 bytes of ICW are needed. + pub use_4_byte_icw: bool, + /// "Edge/Level Control Registers", for edge trigger selection. + /// When a particular bit is set, the corresponding IRQ is in level-triggered mode. Otherwise it + /// is in edge-triggered mode. + pub elcr: u8, + pub elcr_mask: u8, +} + +/// The LapicState represents the state of an x86 CPU's Local APIC. +/// The Local APIC consists of 64 128-bit registers, but only the first 32-bits of each register +/// can be used, so this structure only stores the first 32-bits of each register. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct LapicState { + pub regs: [LapicRegister; 64], +} + +pub type LapicRegister = u32; + +// rust arrays longer than 32 need custom implementations of Debug +impl std::fmt::Debug for LapicState { + fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + self.regs[..].fmt(formatter) + } +} + +// rust arrays longer than 32 need custom implementations of PartialEq +impl PartialEq for LapicState { + fn eq(&self, other: &LapicState) -> bool { + self.regs[..] == other.regs[..] + } +} + +// Lapic equality is reflexive, so we impl Eq +impl Eq for LapicState {} + +/// The PitState represents the state of the PIT (aka the Programmable Interval Timer). +/// The state is simply the state of it's three channels. +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PitState { + pub channels: [PitChannelState; 3], + /// Hypervisor-specific flags for setting the pit state. + pub flags: u32, +} + +/// The PitRWMode enum represents the access mode of a PIT channel. +/// Reads and writes to the Pit happen over Port-mapped I/O, which happens one byte at a time, +/// but the count values and latch values are two bytes. So the access mode controls which of the +/// two bytes will be read when. +#[repr(C)] +#[derive(enumn::N, Clone, Copy, Debug, PartialEq, Eq)] +pub enum PitRWMode { + /// None mode means that no access mode has been set. + None = 0, + /// Least mode means all reads/writes will read/write the least significant byte. + Least = 1, + /// Most mode means all reads/writes will read/write the most significant byte. + Most = 2, + /// Both mode means first the least significant byte will be read/written, then the + /// next read/write will read/write the most significant byte. + Both = 3, +} + +/// Convenience implementation for converting from a u8 +impl From<u8> for PitRWMode { + fn from(item: u8) -> Self { + PitRWMode::n(item).unwrap_or_else(|| { + error!("Invalid PitRWMode value {}, setting to 0", item); + PitRWMode::None + }) + } +} + +/// The PitRWState enum represents the state of reading to or writing from a channel. +/// This is related to the PitRWMode, it mainly gives more detail about the state of the channel +/// with respect to PitRWMode::Both. +#[repr(C)] +#[derive(enumn::N, Clone, Copy, Debug, PartialEq, Eq)] +pub enum PitRWState { + /// None mode means that no access mode has been set. + None = 0, + /// LSB means that the channel is in PitRWMode::Least access mode. + LSB = 1, + /// MSB means that the channel is in PitRWMode::Most access mode. + MSB = 2, + /// Word0 means that the channel is in PitRWMode::Both mode, and the least sginificant byte + /// has not been read/written yet. + Word0 = 3, + /// Word1 means that the channel is in PitRWMode::Both mode and the least significant byte + /// has already been read/written, and the next byte to be read/written will be the most + /// significant byte. + Word1 = 4, +} + +/// Convenience implementation for converting from a u8 +impl From<u8> for PitRWState { + fn from(item: u8) -> Self { + PitRWState::n(item).unwrap_or_else(|| { + error!("Invalid PitRWState value {}, setting to 0", item); + PitRWState::None + }) + } +} + +/// The PitChannelState represents the state of one of the PIT's three counters. +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PitChannelState { + /// The starting value for the counter. + pub count: u32, + /// Stores the channel count from the last time the count was latched. + pub latched_count: u16, + /// Indicates the PitRWState state of reading the latch value. + pub count_latched: PitRWState, + /// Indicates whether ReadBack status has been latched. + pub status_latched: bool, + /// Stores the channel status from the last time the status was latched. The status contains + /// information about the access mode of this channel, but changing those bits in the status + /// will not change the behavior of the pit. + pub status: u8, + /// Indicates the PitRWState state of reading the counter. + pub read_state: PitRWState, + /// Indicates the PitRWState state of writing the counter. + pub write_state: PitRWState, + /// Stores the value with which the counter was initialized. Counters are 16- + /// bit values with an effective range of 1-65536 (65536 represented by 0). + pub reload_value: u16, + /// The command access mode of this channel. + pub rw_mode: PitRWMode, + /// The operation mode of this channel. + pub mode: u8, + /// Whether or not we are in bcd mode. Not supported by KVM or crosvm's PIT implementation. + pub bcd: bool, + /// Value of the gate input pin. This only applies to channel 2. + pub gate: bool, + /// Guest boot nanosecond timestamp of when the count value was loaded. + pub count_load_time: u64, +} diff --git a/msg_socket/src/lib.rs b/msg_socket/src/lib.rs index 7f5d1a6..540d49d 100644 --- a/msg_socket/src/lib.rs +++ b/msg_socket/src/lib.rs @@ -4,7 +4,7 @@ mod msg_on_socket; -use std::io::Result; +use std::io::{IoSlice, Result}; use std::marker::PhantomData; use std::os::unix::io::{AsRawFd, RawFd}; use std::pin::Pin; @@ -138,7 +138,8 @@ pub trait MsgSender: AsRef<UnixSeqpacket> { handle_eintr!(sock.send(&msg_buffer)) .map_err(|e| MsgError::Send(SysError::new(e.raw_os_error().unwrap_or(0))))?; } else { - sock.send_with_fds(&msg_buffer[..], &fd_buffer[0..fd_size]) + let ioslice = IoSlice::new(&msg_buffer[..]); + sock.send_with_fds(&[ioslice], &fd_buffer[0..fd_size]) .map_err(MsgError::Send)?; } Ok(()) diff --git a/resources/src/address_allocator.rs b/resources/src/address_allocator.rs index 11978ee..88f652a 100644 --- a/resources/src/address_allocator.rs +++ b/resources/src/address_allocator.rs @@ -3,7 +3,7 @@ // found in the LICENSE file. use std::cmp; -use std::collections::HashMap; +use std::collections::{BTreeSet, HashMap}; use crate::{Alloc, Error, Result}; @@ -26,11 +26,9 @@ use crate::{Alloc, Error, Result}; /// ``` #[derive(Debug, Eq, PartialEq)] pub struct AddressAllocator { - pool_base: u64, - pool_end: u64, alignment: u64, - next_addr: u64, allocs: HashMap<Alloc, (u64, u64, String)>, + regions: BTreeSet<(u64, u64)>, } impl AddressAllocator { @@ -55,12 +53,12 @@ impl AddressAllocator { if !alignment.is_power_of_two() || alignment == 0 { return Err(Error::BadAlignment); } + let mut regions = BTreeSet::new(); + regions.insert((pool_base, pool_end)); Ok(AddressAllocator { - pool_base, - pool_end, alignment, - next_addr: pool_base, allocs: HashMap::new(), + regions, }) } @@ -85,36 +83,138 @@ impl AddressAllocator { if !alignment.is_power_of_two() { return Err(Error::BadAlignment); } - let align_adjust = if self.next_addr % alignment != 0 { - alignment - (self.next_addr % alignment) - } else { - 0 - }; - let addr = self - .next_addr - .checked_add(align_adjust) - .ok_or(Error::OutOfSpace)?; - let end_addr = addr.checked_add(size - 1).ok_or(Error::OutOfSpace)?; - if end_addr > self.pool_end { - return Err(Error::OutOfSpace); - } - // TODO(dgreid): Use a smarter allocation strategy. The current strategy is just - // bumping this pointer, meaning it will eventually exhaust available addresses. - self.next_addr = end_addr.saturating_add(1); + // finds first region matching alignment and size. + match self + .regions + .iter() + .find(|range| { + match range.0 % alignment { + 0 => range.0.checked_add(size - 1), + r => range.0.checked_add(size - 1 + alignment - r), + } + .map_or(false, |end| end <= range.1) + }) + .cloned() + { + Some(slot) => { + self.regions.remove(&slot); + let start = match slot.0 % alignment { + 0 => slot.0, + r => slot.0 + alignment - r, + }; + let end = start + size - 1; + if slot.0 < start { + self.regions.insert((slot.0, start - 1)); + } + if slot.1 > end { + self.regions.insert((end + 1, slot.1)); + } + self.allocs.insert(alloc, (start, size, tag)); - self.allocs.insert(alloc, (addr, size, tag)); - Ok(addr) + Ok(start) + } + None => Err(Error::OutOfSpace), + } } pub fn allocate(&mut self, size: u64, alloc: Alloc, tag: String) -> Result<u64> { self.allocate_with_align(size, alloc, tag, self.alignment) } + /// Allocates a range of addresses from the managed region with an optional tag + /// and required location. Allocation alignment is not enforced. + /// Returns OutOfSpace if requested range is not available (e.g. already allocated + /// with a different alloc tag). + pub fn allocate_at(&mut self, start: u64, size: u64, alloc: Alloc, tag: String) -> Result<()> { + if self.allocs.contains_key(&alloc) { + return Err(Error::ExistingAlloc(alloc)); + } + if size == 0 { + return Err(Error::AllocSizeZero); + } + + let end = start.checked_add(size - 1).ok_or(Error::OutOfSpace)?; + match self + .regions + .iter() + .find(|range| range.0 <= start && range.1 >= end) + .cloned() + { + Some(slot) => { + self.regions.remove(&slot); + if slot.0 < start { + self.regions.insert((slot.0, start - 1)); + } + if slot.1 > end { + self.regions.insert((end + 1, slot.1)); + } + self.allocs.insert(alloc, (start, size, tag)); + + Ok(()) + } + None => Err(Error::OutOfSpace), + } + } + + /// Releases exising allocation back to free pool. + pub fn release(&mut self, alloc: Alloc) -> Result<()> { + self.allocs + .remove(&alloc) + .map_or_else(|| Err(Error::BadAlloc(alloc)), |v| self.insert_at(v.0, v.1)) + } + /// Returns allocation associated with `alloc`, or None if no such allocation exists. pub fn get(&self, alloc: &Alloc) -> Option<&(u64, u64, String)> { self.allocs.get(alloc) } + + /// Insert range of addresses into the pool, coalescing neighboring regions. + fn insert_at(&mut self, start: u64, size: u64) -> Result<()> { + if size == 0 { + return Err(Error::AllocSizeZero); + } + + let mut slot = (start, start.checked_add(size - 1).ok_or(Error::OutOfSpace)?); + let mut left = None; + let mut right = None; + // simple coalescing with linear search over free regions. + // + // Calculating the distance between start and end of two regions we can + // detect if they are disjoint (>1), adjacent (=1) or possibly + // overlapping (<1). Saturating arithmetic is used to avoid overflow. + // Overlapping regions are detected if both oposite ends are overlapping. + // Algorithm assumes all existing regions are disjoined and represented + // as pair of inclusive location point (start, end), where end >= start. + for range in self.regions.iter() { + match ( + slot.0.saturating_sub(range.1), + range.0.saturating_sub(slot.1), + ) { + (1, 0) => { + left = Some(*range); + } + (0, 1) => { + right = Some(*range); + } + (0, 0) => { + return Err(Error::RegionOverlap { base: start, size }); + } + (_, _) => (), + } + } + if let Some(left) = left { + self.regions.remove(&left); + slot.0 = left.0; + } + if let Some(right) = right { + self.regions.remove(&right); + slot.1 = right.1; + } + self.regions.insert(slot); + + Ok(()) + } } #[cfg(test)] @@ -172,6 +272,44 @@ mod tests { } #[test] + fn allocate_with_special_alignment() { + let mut pool = AddressAllocator::new(0x1000, 0x1000, Some(0x100)).unwrap(); + assert_eq!( + pool.allocate(0x10, Alloc::Anon(0), String::from("bar0")), + Ok(0x1000) + ); + assert_eq!( + pool.allocate_at(0x1200, 0x100, Alloc::Anon(1), String::from("bar1")), + Ok(()) + ); + assert_eq!( + pool.allocate_with_align(0x800, Alloc::Anon(2), String::from("bar2"), 0x800), + Ok(0x1800) + ); + } + + #[test] + fn allocate_and_split_allocate_at() { + let mut pool = AddressAllocator::new(0x1000, 0x1000, Some(0x100)).unwrap(); + assert_eq!( + pool.allocate_at(0x1200, 0x800, Alloc::Anon(0), String::from("bar0")), + Ok(()) + ); + assert_eq!( + pool.allocate(0x800, Alloc::Anon(1), String::from("bar1")), + Err(Error::OutOfSpace) + ); + assert_eq!( + pool.allocate(0x600, Alloc::Anon(2), String::from("bar2")), + Ok(0x1a00) + ); + assert_eq!( + pool.allocate(0x200, Alloc::Anon(3), String::from("bar3")), + Ok(0x1000) + ); + } + + #[test] fn allocate_alignment() { let mut pool = AddressAllocator::new(0x1000, 0x10000, Some(0x100)).unwrap(); assert_eq!( @@ -243,4 +381,45 @@ mod tests { .allocate_with_align(0x110, Alloc::Anon(0), String::from("bar0"), 200) .is_err()); } + + #[test] + fn allocate_with_release() { + let mut pool = AddressAllocator::new(0x1000, 0x1000, None).unwrap(); + assert_eq!( + pool.allocate_with_align(0x100, Alloc::Anon(0), String::from("bar0"), 0x100), + Ok(0x1000) + ); + assert!(pool.release(Alloc::Anon(0)).is_ok()); + assert_eq!( + pool.allocate_with_align(0x1000, Alloc::Anon(0), String::from("bar0"), 0x100), + Ok(0x1000) + ); + } + + #[test] + fn coalescing_and_overlap() { + let mut pool = AddressAllocator::new(0x1000, 0x1000, None).unwrap(); + assert!(pool.insert_at(0x3000, 0x1000).is_ok()); + assert!(pool.insert_at(0x1fff, 0x20).is_err()); + assert!(pool.insert_at(0x2ff1, 0x10).is_err()); + assert!(pool.insert_at(0x1800, 0x1000).is_err()); + assert!(pool.insert_at(0x2000, 0x1000).is_ok()); + assert_eq!( + pool.allocate(0x3000, Alloc::Anon(0), String::from("bar0")), + Ok(0x1000) + ); + } + + #[test] + fn coalescing_single_addresses() { + let mut pool = AddressAllocator::new(0x1000, 0x1000, None).unwrap(); + assert!(pool.insert_at(0x2001, 1).is_ok()); + assert!(pool.insert_at(0x2003, 1).is_ok()); + assert!(pool.insert_at(0x2000, 1).is_ok()); + assert!(pool.insert_at(0x2002, 1).is_ok()); + assert_eq!( + pool.allocate(0x1004, Alloc::Anon(0), String::from("bar0")), + Ok(0x1000) + ); + } } diff --git a/resources/src/lib.rs b/resources/src/lib.rs index cf36cc1..6195e91 100644 --- a/resources/src/lib.rs +++ b/resources/src/lib.rs @@ -46,6 +46,8 @@ pub enum Error { OutOfSpace, PoolOverflow { base: u64, size: u64 }, PoolSizeZero, + RegionOverlap { base: u64, size: u64 }, + BadAlloc(Alloc), } pub type Result<T> = std::result::Result<T, Error>; @@ -64,6 +66,10 @@ impl Display for Error { OutOfSpace => write!(f, "Out of space"), PoolOverflow { base, size } => write!(f, "base={} + size={} overflows", base, size), PoolSizeZero => write!(f, "Pool cannot have size of 0"), + RegionOverlap { base, size } => { + write!(f, "Overlapping region base={} size={}", base, size) + } + BadAlloc(tag) => write!(f, "Alloc does not exists: {:?}", tag), } } } diff --git a/seccomp/x86_64/video_device.policy b/seccomp/x86_64/video_device.policy new file mode 100644 index 0000000..fdc5935 --- /dev/null +++ b/seccomp/x86_64/video_device.policy @@ -0,0 +1,24 @@ +# 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. + +@include /usr/share/policy/crosvm/common_device.policy + +# Syscalls specific to video devices. +clock_getres: 1 +connect: 1 +fcntl: arg1 == F_GETFL || arg1 == F_SETFL || arg1 == F_DUPFD_CLOEXEC || arg1 == F_GETFD || arg1 == F_SETFD +getdents: 1 +getegid: 1 +geteuid: 1 +getgid: 1 +getresgid: 1 +getresuid: 1 +getsockname: 1 +getuid: 1 +# ioctl: arg1 == DRM_IOCTL_* +ioctl: arg1 & 0x6400 +openat: 1 +setpriority: 1 +socket: arg0 == AF_UNIX +stat: 1 diff --git a/src/crosvm.rs b/src/crosvm.rs index 49a08c0..1ea7e0d 100644 --- a/src/crosvm.rs +++ b/src/crosvm.rs @@ -202,6 +202,9 @@ pub struct Config { pub virtio_input_evdevs: Vec<PathBuf>, pub split_irqchip: bool, pub vfio: Vec<PathBuf>, + pub video_dec: bool, + pub video_enc: bool, + pub acpi_tables: Vec<PathBuf>, } impl Default for Config { @@ -250,6 +253,9 @@ impl Default for Config { virtio_input_evdevs: Vec::new(), split_irqchip: false, vfio: Vec::new(), + video_dec: false, + video_enc: false, + acpi_tables: Vec::new(), } } } diff --git a/src/linux.rs b/src/linux.rs index 574493d..3e2045f 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -26,6 +26,8 @@ use std::time::Duration; use libc::{self, c_int, gid_t, uid_t}; +use acpi_tables::sdt::SDT; + #[cfg(feature = "gpu")] use devices::virtio::EventDevice; use devices::virtio::{self, Console, VirtioDevice}; @@ -109,6 +111,7 @@ pub enum Error { LoadKernel(Box<dyn StdError>), MemoryTooLarge, NetDeviceNew(virtio::NetError), + OpenAcpiTable(PathBuf, io::Error), OpenAndroidFstab(PathBuf, io::Error), OpenBios(PathBuf, io::Error), OpenInitrd(PathBuf, io::Error), @@ -196,6 +199,7 @@ impl Display for Error { LoadKernel(e) => write!(f, "failed to load kernel: {}", e), MemoryTooLarge => write!(f, "requested memory size too large"), NetDeviceNew(e) => write!(f, "failed to set up virtio networking: {}", e), + OpenAcpiTable(p, e) => write!(f, "failed to open ACPI file {}: {}", p.display(), e), OpenAndroidFstab(p, e) => write!( f, "failed to open android fstab file {}: {}", @@ -792,6 +796,70 @@ fn create_wayland_device( }) } +#[cfg(any(feature = "video-decoder", feature = "video-encoder"))] +fn create_video_device( + cfg: &Config, + typ: devices::virtio::VideoDeviceType, + resource_bridge: virtio::resource_bridge::ResourceRequestSocket, +) -> DeviceResult { + let jail = match simple_jail(&cfg, "video_device")? { + Some(mut jail) => { + match typ { + devices::virtio::VideoDeviceType::Decoder => { + add_crosvm_user_to_jail(&mut jail, "video-decoder")? + } + devices::virtio::VideoDeviceType::Encoder => { + add_crosvm_user_to_jail(&mut jail, "video-encoder")? + } + }; + + // Create a tmpfs in the device's root directory so that we can bind mount files. + jail.mount_with_data( + Path::new("none"), + Path::new("/"), + "tmpfs", + (libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC) as usize, + "size=67108864", + )?; + + // Render node for libvda. + let dev_dri_path = Path::new("/dev/dri/renderD128"); + jail.mount_bind(dev_dri_path, dev_dri_path, false)?; + + // Device nodes required by libchrome which establishes Mojo connection in libvda. + let dev_urandom_path = Path::new("/dev/urandom"); + jail.mount_bind(dev_urandom_path, dev_urandom_path, false)?; + let system_bus_socket_path = Path::new("/run/dbus/system_bus_socket"); + jail.mount_bind(system_bus_socket_path, system_bus_socket_path, true)?; + + Some(jail) + } + None => None, + }; + + Ok(VirtioDeviceStub { + dev: Box::new(devices::virtio::VideoDevice::new( + typ, + Some(resource_bridge), + )), + jail, + }) +} + +#[cfg(any(feature = "video-decoder", feature = "video-encoder"))] +fn register_video_device( + devs: &mut Vec<VirtioDeviceStub>, + resource_bridges: &mut Vec<virtio::resource_bridge::ResourceResponseSocket>, + cfg: &Config, + typ: devices::virtio::VideoDeviceType, +) -> std::result::Result<(), Error> { + let (video_socket, gpu_socket) = + virtio::resource_bridge::pair().map_err(Error::CreateSocket)?; + resource_bridges.push(gpu_socket); + devs.push(create_video_device(cfg, typ, video_socket)?); + Ok(()) +} + fn create_vhost_vsock_device(cfg: &Config, cid: u64, mem: &GuestMemory) -> DeviceResult { let dev = virtio::vhost::Vsock::new(cid, mem).map_err(Error::VhostVsockDeviceNew)?; @@ -1088,6 +1156,30 @@ fn create_virtio_devices( )?); } + #[cfg(feature = "video-decoder")] + { + if cfg.video_dec { + register_video_device( + &mut devs, + &mut resource_bridges, + cfg, + devices::virtio::VideoDeviceType::Decoder, + )?; + } + } + + #[cfg(feature = "video-encoder")] + { + if cfg.video_enc { + register_video_device( + &mut devs, + &mut resource_bridges, + cfg, + devices::virtio::VideoDeviceType::Encoder, + )?; + } + } + #[cfg(feature = "gpu")] { if let Some(gpu_parameters) = &cfg.gpu_parameters { @@ -1624,6 +1716,11 @@ pub fn run_config(cfg: Config) -> Result<()> { initrd_image, extra_kernel_params: cfg.params.clone(), wayland_dmabuf: cfg.wayland_dmabuf, + acpi_sdts: cfg + .acpi_tables + .iter() + .map(|path| SDT::from_file(path).map_err(|e| Error::OpenAcpiTable(path.clone(), e))) + .collect::<Result<Vec<SDT>>>()?, }; let control_server_socket = match &cfg.socket_path { diff --git a/src/main.rs b/src/main.rs index 7fd3eca..ed7bb30 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1223,6 +1223,29 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: cfg.vfio.push(vfio_path); } + "video-decoder" => { + cfg.video_dec = true; + } + "video-encoder" => { + cfg.video_enc = true; + } + "acpi-table" => { + let acpi_table = PathBuf::from(value.unwrap()); + if !acpi_table.exists() { + return Err(argument::Error::InvalidValue { + value: value.unwrap().to_owned(), + expected: String::from("the acpi-table path does not exist"), + }); + } + if !acpi_table.is_file() { + return Err(argument::Error::InvalidValue { + value: value.unwrap().to_owned(), + expected: String::from("the acpi-table path should be a file"), + }); + } + + cfg.acpi_tables.push(acpi_table); + } "help" => return Err(argument::Error::PrintHelp), _ => unreachable!(), @@ -1677,6 +1700,18 @@ Enable split-irqchip support. "PATH", "Path to sysfs of pass through or mdev device", ), + #[cfg(feature = "video-decoder")] + Argument::flag("video-decoder", "\ +(EXPERIMENTAL) +Enable virtio-video decoder device. +", + ), + #[cfg(feature = "video-encoder")] + Argument::flag("video-encoder", "\ +(EXPERIMENTAL) +Enable virtio-video encoder device. +"), + Argument::value("acpi-table", "PATH", "Path to user provided ACPI table."), Argument::short_flag('h', "help", "Print help message."), ]; diff --git a/src/plugin/process.rs b/src/plugin/process.rs index 783239a..688aa85 100644 --- a/src/plugin/process.rs +++ b/src/plugin/process.rs @@ -5,7 +5,7 @@ use std::collections::hash_map::{Entry, HashMap, VacantEntry}; use std::env::set_var; use std::fs::File; -use std::io::Write; +use std::io::{IoSlice, Write}; use std::mem::transmute; use std::os::unix::io::{IntoRawFd, RawFd}; use std::os::unix::net::UnixDatagram; @@ -730,7 +730,7 @@ impl Process { .map_err(Error::EncodeResponse)?; assert_ne!(self.response_buffer.len(), 0); self.request_sockets[index] - .send_with_fds(&self.response_buffer[..], &response_fds) + .send_with_fds(&[IoSlice::new(&self.response_buffer[..])], &response_fds) .map_err(Error::PluginSocketSend)?; Ok(()) diff --git a/sys_util/src/file_traits.rs b/sys_util/src/file_traits.rs index 54e710f..bd763c7 100644 --- a/sys_util/src/file_traits.rs +++ b/sys_util/src/file_traits.rs @@ -98,7 +98,7 @@ pub trait FileReadWriteVolatile { } // Will panic if read_volatile read more bytes than we gave it, which would be worthy of // a panic. - slice = slice.offset(bytes_read as u64).unwrap(); + slice = slice.offset(bytes_read).unwrap(); } Ok(()) } @@ -129,7 +129,7 @@ pub trait FileReadWriteVolatile { } // Will panic if read_volatile read more bytes than we gave it, which would be worthy of // a panic. - slice = slice.offset(bytes_written as u64).unwrap(); + slice = slice.offset(bytes_written).unwrap(); } Ok(()) } @@ -187,7 +187,7 @@ pub trait FileReadWriteAtVolatile { match self.read_at_volatile(slice, offset) { Ok(0) => return Err(Error::from(ErrorKind::UnexpectedEof)), Ok(n) => { - slice = slice.offset(n as u64).unwrap(); + slice = slice.offset(n).unwrap(); offset = offset.checked_add(n as u64).unwrap(); } Err(ref e) if e.kind() == ErrorKind::Interrupted => {} @@ -221,7 +221,7 @@ pub trait FileReadWriteAtVolatile { match self.write_at_volatile(slice, offset) { Ok(0) => return Err(Error::from(ErrorKind::WriteZero)), Ok(n) => { - slice = slice.offset(n as u64).unwrap(); + slice = slice.offset(n).unwrap(); offset = offset.checked_add(n as u64).unwrap(); } Err(ref e) if e.kind() == ErrorKind::Interrupted => {} @@ -282,7 +282,7 @@ macro_rules! volatile_impl { let ret = unsafe { $crate::file_traits::lib::read( self.as_raw_fd(), - slice.as_ptr() as *mut std::ffi::c_void, + slice.as_mut_ptr() as *mut std::ffi::c_void, slice.size() as usize, ) }; @@ -297,13 +297,7 @@ macro_rules! volatile_impl { &mut self, bufs: &[$crate::file_traits::lib::VolatileSlice], ) -> std::io::Result<usize> { - let iovecs: Vec<$crate::file_traits::lib::iovec> = bufs - .iter() - .map(|s| $crate::file_traits::lib::iovec { - iov_base: s.as_ptr() as *mut std::ffi::c_void, - iov_len: s.size() as $crate::file_traits::lib::size_t, - }) - .collect(); + let iovecs = $crate::file_traits::lib::VolatileSlice::as_iovecs(bufs); if iovecs.is_empty() { return Ok(0); @@ -314,7 +308,7 @@ macro_rules! volatile_impl { let ret = unsafe { $crate::file_traits::lib::readv( self.as_raw_fd(), - &iovecs[0], + iovecs.as_ptr(), iovecs.len() as std::os::raw::c_int, ) }; @@ -349,13 +343,7 @@ macro_rules! volatile_impl { &mut self, bufs: &[$crate::file_traits::lib::VolatileSlice], ) -> std::io::Result<usize> { - let iovecs: Vec<$crate::file_traits::lib::iovec> = bufs - .iter() - .map(|s| $crate::file_traits::lib::iovec { - iov_base: s.as_ptr() as *mut std::ffi::c_void, - iov_len: s.size() as $crate::file_traits::lib::size_t, - }) - .collect(); + let iovecs = $crate::file_traits::lib::VolatileSlice::as_iovecs(bufs); if iovecs.is_empty() { return Ok(0); @@ -366,7 +354,7 @@ macro_rules! volatile_impl { let ret = unsafe { $crate::file_traits::lib::writev( self.as_raw_fd(), - &iovecs[0], + iovecs.as_ptr(), iovecs.len() as std::os::raw::c_int, ) }; @@ -394,7 +382,7 @@ macro_rules! volatile_at_impl { let ret = unsafe { $crate::file_traits::lib::pread64( self.as_raw_fd(), - slice.as_ptr() as *mut std::ffi::c_void, + slice.as_mut_ptr() as *mut std::ffi::c_void, slice.size() as usize, offset as $crate::file_traits::lib::off64_t, ) @@ -412,13 +400,7 @@ macro_rules! volatile_at_impl { bufs: &[$crate::file_traits::lib::VolatileSlice], offset: u64, ) -> std::io::Result<usize> { - let iovecs: Vec<$crate::file_traits::lib::iovec> = bufs - .iter() - .map(|s| $crate::file_traits::lib::iovec { - iov_base: s.as_ptr() as *mut std::ffi::c_void, - iov_len: s.size() as $crate::file_traits::lib::size_t, - }) - .collect(); + let iovecs = $crate::file_traits::lib::VolatileSlice::as_iovecs(bufs); if iovecs.is_empty() { return Ok(0); @@ -429,7 +411,7 @@ macro_rules! volatile_at_impl { let ret = unsafe { $crate::file_traits::lib::preadv64( self.as_raw_fd(), - &iovecs[0], + iovecs.as_ptr(), iovecs.len() as std::os::raw::c_int, offset as $crate::file_traits::lib::off64_t, ) @@ -469,13 +451,7 @@ macro_rules! volatile_at_impl { bufs: &[$crate::file_traits::lib::VolatileSlice], offset: u64, ) -> std::io::Result<usize> { - let iovecs: Vec<$crate::file_traits::lib::iovec> = bufs - .iter() - .map(|s| $crate::file_traits::lib::iovec { - iov_base: s.as_ptr() as *mut std::ffi::c_void, - iov_len: s.size() as $crate::file_traits::lib::size_t, - }) - .collect(); + let iovecs = $crate::file_traits::lib::VolatileSlice::as_iovecs(bufs); if iovecs.is_empty() { return Ok(0); @@ -486,7 +462,7 @@ macro_rules! volatile_at_impl { let ret = unsafe { $crate::file_traits::lib::pwritev64( self.as_raw_fd(), - &iovecs[0], + iovecs.as_ptr(), iovecs.len() as std::os::raw::c_int, offset as $crate::file_traits::lib::off64_t, ) diff --git a/sys_util/src/guest_memory.rs b/sys_util/src/guest_memory.rs index e8f620b..60b775a 100644 --- a/sys_util/src/guest_memory.rs +++ b/sys_util/src/guest_memory.rs @@ -7,6 +7,7 @@ use std::convert::AsRef; use std::convert::TryFrom; use std::fmt::{self, Display}; +use std::mem::size_of; use std::os::unix::io::{AsRawFd, RawFd}; use std::result; use std::sync::Arc; @@ -87,11 +88,19 @@ struct MemoryRegion { memfd_offset: u64, } -fn region_end(region: &MemoryRegion) -> GuestAddress { - // unchecked_add is safe as the region bounds were checked when it was created. - region - .guest_base - .unchecked_add(region.mapping.size() as u64) +impl MemoryRegion { + fn start(&self) -> GuestAddress { + self.guest_base + } + + fn end(&self) -> GuestAddress { + // unchecked_add is safe as the region bounds were checked when it was created. + self.guest_base.unchecked_add(self.mapping.size() as u64) + } + + fn contains(&self, addr: GuestAddress) -> bool { + addr >= self.guest_base && addr < self.end() + } } /// Tracks a memory region and where it is mapped in the guest, along with a shm @@ -200,8 +209,8 @@ impl GuestMemory { pub fn end_addr(&self) -> GuestAddress { self.regions .iter() - .max_by_key(|region| region.guest_base) - .map_or(GuestAddress(0), |region| region_end(region)) + .max_by_key(|region| region.start()) + .map_or(GuestAddress(0), MemoryRegion::end) } /// Returns the total size of memory in bytes. @@ -214,9 +223,7 @@ impl GuestMemory { /// Returns true if the given address is within the memory range available to the guest. pub fn address_in_range(&self, addr: GuestAddress) -> bool { - self.regions - .iter() - .any(|region| region.guest_base <= addr && addr < region_end(region)) + self.regions.iter().any(|region| region.contains(addr)) } /// Returns true if the given range (start, end) is overlap with the memory range @@ -224,7 +231,7 @@ impl GuestMemory { pub fn range_overlap(&self, start: GuestAddress, end: GuestAddress) -> bool { self.regions .iter() - .any(|region| region.guest_base < end && start < region_end(region)) + .any(|region| region.start() < end && start < region.end()) } /// Returns the address plus the offset if it is in range. @@ -267,7 +274,7 @@ impl GuestMemory { for (index, region) in self.regions.iter().enumerate() { cb( index, - region.guest_base, + region.start(), region.mapping.size(), region.mapping.as_ptr() as usize, region.memfd_offset, @@ -442,6 +449,61 @@ impl GuestMemory { }) } + /// Returns a `VolatileSlice` of `len` bytes starting at `addr`. Returns an error if the slice + /// is not a subset of this `GuestMemory`. + /// + /// # Examples + /// * Write `99` to 30 bytes starting at guest address 0x1010. + /// + /// ``` + /// # use sys_util::{GuestAddress, GuestMemory, GuestMemoryError, MemoryMapping}; + /// # fn test_volatile_slice() -> Result<(), GuestMemoryError> { + /// # let start_addr = GuestAddress(0x1000); + /// # let mut gm = GuestMemory::new(&vec![(start_addr, 0x400)])?; + /// let vslice = gm.get_slice_at_addr(GuestAddress(0x1010), 30)?; + /// vslice.write_bytes(99); + /// # Ok(()) + /// # } + /// ``` + pub fn get_slice_at_addr(&self, addr: GuestAddress, len: usize) -> Result<VolatileSlice> { + self.regions + .iter() + .find(|region| region.contains(addr)) + .ok_or(Error::InvalidGuestAddress(addr)) + .and_then(|region| { + // The cast to a usize is safe here because we know that `region.contains(addr)` and + // it's not possible for a memory region to be larger than what fits in a usize. + region + .mapping + .get_slice(addr.offset_from(region.start()) as usize, len) + .map_err(Error::VolatileMemoryAccess) + }) + } + + /// Returns a `VolatileRef` to an object at `addr`. Returns Ok(()) if the object fits, or Err if + /// it extends past the end. + /// + /// # Examples + /// * Get a &u64 at offset 0x1010. + /// + /// ``` + /// # use sys_util::{GuestAddress, GuestMemory, GuestMemoryError, MemoryMapping}; + /// # fn test_ref_u64() -> Result<(), GuestMemoryError> { + /// # let start_addr = GuestAddress(0x1000); + /// # let mut gm = GuestMemory::new(&vec![(start_addr, 0x400)])?; + /// gm.write_obj_at_addr(47u64, GuestAddress(0x1010))?; + /// let vref = gm.get_ref_at_addr::<u64>(GuestAddress(0x1010))?; + /// assert_eq!(vref.load(), 47u64); + /// # Ok(()) + /// # } + /// ``` + pub fn get_ref_at_addr<T: DataInit>(&self, addr: GuestAddress) -> Result<VolatileRef<T>> { + let buf = self.get_slice_at_addr(addr, size_of::<T>())?; + // Safe because we have know that `buf` is at least `size_of::<T>()` bytes and that the + // returned reference will not outlive this `GuestMemory`. + Ok(unsafe { VolatileRef::new(buf.as_mut_ptr() as *mut T) }) + } + /// Reads data from a file descriptor and writes it to guest memory. /// /// # Arguments @@ -550,15 +612,16 @@ impl GuestMemory { where F: FnOnce(&MemoryMapping, usize) -> Result<T>, { - for region in self.regions.iter() { - if guest_addr >= region.guest_base && guest_addr < region_end(region) { - return cb( + self.regions + .iter() + .find(|region| region.contains(guest_addr)) + .ok_or(Error::InvalidGuestAddress(guest_addr)) + .and_then(|region| { + cb( ®ion.mapping, - guest_addr.offset_from(region.guest_base) as usize, - ); - } - } - Err(Error::InvalidGuestAddress(guest_addr)) + guest_addr.offset_from(region.start()) as usize, + ) + }) } /// Convert a GuestAddress into an offset within self.memfd. @@ -585,25 +648,11 @@ impl GuestMemory { /// assert_eq!(offset, 0x3500); /// ``` pub fn offset_from_base(&self, guest_addr: GuestAddress) -> Result<u64> { - for region in self.regions.iter() { - if guest_addr >= region.guest_base && guest_addr < region_end(region) { - return Ok(region.memfd_offset + guest_addr.offset_from(region.guest_base) as u64); - } - } - Err(Error::InvalidGuestAddress(guest_addr)) - } -} - -impl VolatileMemory for GuestMemory { - fn get_slice(&self, offset: u64, count: u64) -> VolatileMemoryResult<VolatileSlice> { - for region in self.regions.iter() { - if offset >= region.guest_base.0 && offset < region_end(region).0 { - return region - .mapping - .get_slice(offset - region.guest_base.0, count); - } - } - Err(VolatileMemoryError::OutOfBounds { addr: offset }) + self.regions + .iter() + .find(|region| region.contains(guest_addr)) + .ok_or(Error::InvalidGuestAddress(guest_addr)) + .map(|region| region.memfd_offset + guest_addr.offset_from(region.start())) } } @@ -690,8 +739,11 @@ mod tests { gm.write_obj_at_addr(val1, GuestAddress(0x500)).unwrap(); gm.write_obj_at_addr(val2, GuestAddress(0x1000 + 32)) .unwrap(); - let num1: u64 = gm.get_ref(0x500).unwrap().load(); - let num2: u64 = gm.get_ref(0x1000 + 32).unwrap().load(); + let num1: u64 = gm.get_ref_at_addr(GuestAddress(0x500)).unwrap().load(); + let num2: u64 = gm + .get_ref_at_addr(GuestAddress(0x1000 + 32)) + .unwrap() + .load(); assert_eq!(val1, num1); assert_eq!(val2, num2); } @@ -704,8 +756,10 @@ mod tests { let val1: u64 = 0xaa55aa55aa55aa55; let val2: u64 = 0x55aa55aa55aa55aa; - gm.get_ref(0x500).unwrap().store(val1); - gm.get_ref(0x1000 + 32).unwrap().store(val2); + gm.get_ref_at_addr(GuestAddress(0x500)).unwrap().store(val1); + gm.get_ref_at_addr(GuestAddress(0x1000 + 32)) + .unwrap() + .store(val2); let num1: u64 = gm.read_obj_from_addr(GuestAddress(0x500)).unwrap(); let num2: u64 = gm.read_obj_from_addr(GuestAddress(0x1000 + 32)).unwrap(); assert_eq!(val1, num1); diff --git a/sys_util/src/mmap.rs b/sys_util/src/mmap.rs index c6a52ea..64ffe17 100644 --- a/sys_util/src/mmap.rs +++ b/sys_util/src/mmap.rs @@ -608,15 +608,23 @@ impl MemoryMapping { } impl VolatileMemory for MemoryMapping { - fn get_slice(&self, offset: u64, count: u64) -> VolatileMemoryResult<VolatileSlice> { + fn get_slice(&self, offset: usize, count: usize) -> VolatileMemoryResult<VolatileSlice> { let mem_end = calc_offset(offset, count)?; - if mem_end > self.size as u64 { + if mem_end > self.size { return Err(VolatileMemoryError::OutOfBounds { addr: mem_end }); } + let new_addr = + (self.as_ptr() as usize) + .checked_add(offset) + .ok_or(VolatileMemoryError::Overflow { + base: self.as_ptr() as usize, + offset, + })?; + // Safe because we checked that offset + count was within our range and we only ever hand // out volatile accessors. - Ok(unsafe { VolatileSlice::new((self.addr as usize + offset as usize) as *mut _, count) }) + Ok(unsafe { VolatileSlice::from_raw_parts(new_addr as *mut u8, count) }) } } @@ -888,11 +896,11 @@ mod tests { #[test] fn slice_overflow_error() { let m = MemoryMapping::new(5).unwrap(); - let res = m.get_slice(std::u64::MAX, 3).unwrap_err(); + let res = m.get_slice(std::usize::MAX, 3).unwrap_err(); assert_eq!( res, VolatileMemoryError::Overflow { - base: std::u64::MAX, + base: std::usize::MAX, offset: 3, } ); diff --git a/sys_util/src/sock_ctrl_msg.rs b/sys_util/src/sock_ctrl_msg.rs index d4b953b..77a724d 100644 --- a/sys_util/src/sock_ctrl_msg.rs +++ b/sys_util/src/sock_ctrl_msg.rs @@ -6,10 +6,12 @@ //! (e.g. Unix domain sockets). use std::fs::File; +use std::io::{IoSlice, IoSliceMut}; use std::mem::size_of; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::os::unix::net::{UnixDatagram, UnixStream}; use std::ptr::{copy_nonoverlapping, null_mut, write_unaligned}; +use std::slice; use libc::{ c_long, c_void, cmsghdr, iovec, msghdr, recvmsg, sendmsg, MSG_NOSIGNAL, SCM_RIGHTS, SOL_SOCKET, @@ -103,17 +105,17 @@ impl CmsgBuffer { } } -fn raw_sendmsg<D: IntoIovec>(fd: RawFd, out_data: D, out_fds: &[RawFd]) -> Result<usize> { +fn raw_sendmsg<D: IntoIovec>(fd: RawFd, out_data: &[D], out_fds: &[RawFd]) -> Result<usize> { let cmsg_capacity = CMSG_SPACE!(size_of::<RawFd>() * out_fds.len()); let mut cmsg_buffer = CmsgBuffer::with_capacity(cmsg_capacity); - let mut iovec = out_data.into_iovec(); + let iovec = IntoIovec::as_iovecs(out_data); let mut msg = msghdr { msg_name: null_mut(), msg_namelen: 0, - msg_iov: iovec.as_mut_ptr(), - msg_iovlen: 1, + msg_iov: iovec.as_ptr() as *mut iovec, + msg_iovlen: iovec.len(), msg_control: null_mut(), msg_controllen: 0, msg_flags: 0, @@ -230,7 +232,7 @@ pub trait ScmSocket { /// /// * `buf` - A buffer of data to send on the `socket`. /// * `fd` - A file descriptors to be sent. - fn send_with_fd<D: IntoIovec>(&self, buf: D, fd: RawFd) -> Result<usize> { + fn send_with_fd<D: IntoIovec>(&self, buf: &[D], fd: RawFd) -> Result<usize> { self.send_with_fds(buf, &[fd]) } @@ -242,7 +244,7 @@ pub trait ScmSocket { /// /// * `buf` - A buffer of data to send on the `socket`. /// * `fds` - A list of file descriptors to be sent. - fn send_with_fds<D: IntoIovec>(&self, buf: D, fd: &[RawFd]) -> Result<usize> { + fn send_with_fds<D: IntoIovec>(&self, buf: &[D], fd: &[RawFd]) -> Result<usize> { raw_sendmsg(self.socket_fd(), buf, fd) } @@ -307,30 +309,55 @@ impl ScmSocket for UnixSeqpacket { /// /// This trait is unsafe because interfaces that use this trait depend on the base pointer and size /// being accurate. -pub unsafe trait IntoIovec { - /// Gets a vector of structures describing each contiguous region of a memory buffer. - fn into_iovec(&self) -> Vec<libc::iovec>; +pub unsafe trait IntoIovec: Sized { + /// Returns a `iovec` that describes a contiguous region of memory. + fn into_iovec(&self) -> iovec; + + /// Returns a slice of `iovec`s that each describe a contiguous region of memory. + fn as_iovecs(bufs: &[Self]) -> &[iovec]; +} + +// Safe because there are no other mutable references to the memory described by `IoSlice` and it is +// guaranteed to be ABI-compatible with `iovec`. +unsafe impl<'a> IntoIovec for IoSlice<'a> { + fn into_iovec(&self) -> iovec { + iovec { + iov_base: self.as_ptr() as *mut c_void, + iov_len: self.len(), + } + } + + fn as_iovecs(bufs: &[Self]) -> &[iovec] { + // Safe because `IoSlice` is guaranteed to be ABI-compatible with `iovec`. + unsafe { slice::from_raw_parts(bufs.as_ptr() as *const iovec, bufs.len()) } + } } -// Safe because this slice can not have another mutable reference and it's pointer and size are -// guaranteed to be valid. -unsafe impl<'a> IntoIovec for &'a [u8] { - fn into_iovec(&self) -> Vec<libc::iovec> { - vec![libc::iovec { - iov_base: self.as_ref().as_ptr() as *const c_void as *mut c_void, +// Safe because there are no other references to the memory described by `IoSliceMut` and it is +// guaranteed to be ABI-compatible with `iovec`. +unsafe impl<'a> IntoIovec for IoSliceMut<'a> { + fn into_iovec(&self) -> iovec { + iovec { + iov_base: self.as_ptr() as *mut c_void, iov_len: self.len(), - }] + } + } + + fn as_iovecs(bufs: &[Self]) -> &[iovec] { + // Safe because `IoSliceMut` is guaranteed to be ABI-compatible with `iovec`. + unsafe { slice::from_raw_parts(bufs.as_ptr() as *const iovec, bufs.len()) } } } // Safe because volatile slices are only ever accessed with other volatile interfaces and the // pointer and size are guaranteed to be accurate. unsafe impl<'a> IntoIovec for VolatileSlice<'a> { - fn into_iovec(&self) -> Vec<libc::iovec> { - vec![libc::iovec { - iov_base: self.as_ptr() as *const c_void as *mut c_void, - iov_len: self.size() as usize, - }] + fn into_iovec(&self) -> iovec { + self.as_iovec() + } + + fn as_iovecs(bufs: &[Self]) -> &[iovec] { + VolatileSlice::as_iovecs(bufs) } } @@ -388,8 +415,9 @@ mod tests { fn send_recv_no_fd() { let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); + let ioslice = IoSlice::new([1u8, 1, 2, 21, 34, 55].as_ref()); let write_count = s1 - .send_with_fds([1u8, 1, 2, 21, 34, 55].as_ref(), &[]) + .send_with_fds(&[ioslice], &[]) .expect("failed to send data"); assert_eq!(write_count, 6); @@ -410,8 +438,9 @@ mod tests { let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); let evt = EventFd::new().expect("failed to create eventfd"); + let ioslice = IoSlice::new([].as_ref()); let write_count = s1 - .send_with_fd([].as_ref(), evt.as_raw_fd()) + .send_with_fd(&[ioslice], evt.as_raw_fd()) .expect("failed to send fd"); assert_eq!(write_count, 0); @@ -437,8 +466,9 @@ mod tests { let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); let evt = EventFd::new().expect("failed to create eventfd"); + let ioslice = IoSlice::new([237].as_ref()); let write_count = s1 - .send_with_fds([237].as_ref(), &[evt.as_raw_fd()]) + .send_with_fds(&[ioslice], &[evt.as_raw_fd()]) .expect("failed to send fd"); assert_eq!(write_count, 1); diff --git a/x86_64/src/acpi.rs b/x86_64/src/acpi.rs index c1d92ee..e6fc15a 100644 --- a/x86_64/src/acpi.rs +++ b/x86_64/src/acpi.rs @@ -8,6 +8,8 @@ use sys_util::{GuestAddress, GuestMemory}; pub struct ACPIDevResource { pub amls: Vec<u8>, pub pm_iobase: u64, + /// Additional system descriptor tables. + pub sdts: Vec<SDT>, } #[repr(C)] @@ -102,17 +104,35 @@ pub fn create_acpi_tables( num_cpus: u8, sci_irq: u32, acpi_dev_resource: ACPIDevResource, -) -> GuestAddress { +) -> Option<GuestAddress> { // RSDP is at the HI RSDP WINDOW let rsdp_offset = GuestAddress(super::ACPI_HI_RSDP_WINDOW_BASE); + let mut offset = rsdp_offset.checked_add(RSDP::len() as u64)?; let mut tables: Vec<u64> = Vec::new(); + let mut dsdt_offset: Option<GuestAddress> = None; + + // User supplied System Description Tables, e.g. SSDT. + for sdt in acpi_dev_resource.sdts.iter() { + guest_mem.write_at_addr(sdt.as_slice(), offset).ok()?; + if sdt.is_signature(b"DSDT") { + dsdt_offset = Some(offset); + } else { + tables.push(offset.0); + } + offset = offset.checked_add(sdt.len() as u64)?; + } // DSDT - let dsdt = create_dsdt_table(acpi_dev_resource.amls); - let dsdt_offset = rsdp_offset.checked_add(RSDP::len() as u64).unwrap(); - guest_mem - .write_at_addr(dsdt.as_slice(), dsdt_offset) - .expect("Error writing DSDT table"); + let dsdt_offset = match dsdt_offset { + Some(dsdt_offset) => dsdt_offset, + None => { + let dsdt_offset = offset; + let dsdt = create_dsdt_table(acpi_dev_resource.amls); + guest_mem.write_at_addr(dsdt.as_slice(), offset).ok()?; + offset = offset.checked_add(dsdt.len() as u64)?; + dsdt_offset + } + }; // FACP aka FADT // Revision 6 of the ACPI FADT table is 276 bytes long @@ -160,11 +180,9 @@ pub fn create_acpi_tables( facp.write(FADT_FIELD_HYPERVISOR_ID, *b"CROSVM"); // Hypervisor Vendor Identity - let facp_offset = dsdt_offset.checked_add(dsdt.len() as u64).unwrap(); - guest_mem - .write_at_addr(facp.as_slice(), facp_offset) - .expect("Error writing FACP table"); - tables.push(facp_offset.0); + guest_mem.write_at_addr(facp.as_slice(), offset).ok()?; + tables.push(offset.0); + offset = offset.checked_add(facp.len() as u64)?; // MADT let mut madt = SDT::new( @@ -198,11 +216,9 @@ pub fn create_acpi_tables( ..Default::default() }); - let madt_offset = facp_offset.checked_add(facp.len() as u64).unwrap(); - guest_mem - .write_at_addr(madt.as_slice(), madt_offset) - .expect("Error writing MADT table"); - tables.push(madt_offset.0); + guest_mem.write_at_addr(madt.as_slice(), offset).ok()?; + tables.push(offset.0); + offset = offset.checked_add(madt.len() as u64)?; // XSDT let mut xsdt = SDT::new( @@ -217,16 +233,11 @@ pub fn create_acpi_tables( xsdt.append(table); } - let xsdt_offset = madt_offset.checked_add(madt.len() as u64).unwrap(); - guest_mem - .write_at_addr(xsdt.as_slice(), xsdt_offset) - .expect("Error writing XSDT table"); + guest_mem.write_at_addr(xsdt.as_slice(), offset).ok()?; // RSDP - let rsdp = RSDP::new(*b"CROSVM", xsdt_offset.0); - guest_mem - .write_at_addr(rsdp.as_slice(), rsdp_offset) - .expect("Error writing RSDP"); + let rsdp = RSDP::new(*b"CROSVM", offset.0); + guest_mem.write_at_addr(rsdp.as_slice(), rsdp_offset).ok()?; - rsdp_offset + Some(rsdp_offset) } diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs index b4c4aa7..9714638 100644 --- a/x86_64/src/lib.rs +++ b/x86_64/src/lib.rs @@ -55,6 +55,7 @@ use std::sync::Arc; use crate::bootparam::boot_params; use acpi_tables::aml::Aml; +use acpi_tables::sdt::SDT; use arch::{ get_serial_cmdline, GetSerialCmdlineError, RunnableLinuxVm, SerialHardware, SerialParameters, VmComponents, VmImage, @@ -283,9 +284,11 @@ fn configure_system( .write_obj_at_addr(params, zero_page_addr) .map_err(|_| Error::ZeroPageSetup)?; - let rsdp_addr = - acpi::create_acpi_tables(guest_mem, num_cpus, X86_64_SCI_IRQ, acpi_dev_resource); - params.acpi_rsdp_addr = rsdp_addr.0; + if let Some(rsdp_addr) = + acpi::create_acpi_tables(guest_mem, num_cpus, X86_64_SCI_IRQ, acpi_dev_resource) + { + params.acpi_rsdp_addr = rsdp_addr.0; + } Ok(()) } @@ -442,6 +445,7 @@ impl arch::LinuxArch for X8664arch { &mut io_bus, &mut resources, suspend_evt.try_clone().map_err(Error::CloneEventFd)?, + components.acpi_sdts, )?; let ramoops_region = match components.pstore { @@ -853,6 +857,7 @@ impl X8664arch { io_bus: &mut devices::Bus, resources: &mut SystemAllocator, suspend_evt: EventFd, + sdts: Vec<SDT>, ) -> Result<acpi::ACPIDevResource> { // The AML data for the acpi devices let mut amls = Vec::new(); @@ -883,7 +888,11 @@ impl X8664arch { .unwrap(); io_bus.notify_on_resume(pm); - Ok(acpi::ACPIDevResource { amls, pm_iobase }) + Ok(acpi::ACPIDevResource { + amls, + pm_iobase, + sdts, + }) } /// Sets up the serial devices for this platform. Returns the serial port number and serial diff --git a/x86_64/src/mptable.rs b/x86_64/src/mptable.rs index 218f561..de489f2 100644 --- a/x86_64/src/mptable.rs +++ b/x86_64/src/mptable.rs @@ -10,7 +10,6 @@ use std::slice; use libc::c_char; -use data_model::VolatileMemory; use devices::{PciAddress, PciInterruptPin}; use sys_util::{GuestAddress, GuestMemory}; @@ -140,7 +139,7 @@ pub fn setup_mptable( return Err(Error::AddressOverflow); } - mem.get_slice(base_mp.0, mp_size as u64) + mem.get_slice_at_addr(base_mp, mp_size) .map_err(|_| Error::Clear)? .write_bytes(0); |