diff options
author | Alyssa Ross <hi@alyssa.is> | 2020-06-02 03:03:26 +0000 |
---|---|---|
committer | Alyssa Ross <hi@alyssa.is> | 2020-06-14 11:23:24 +0000 |
commit | 28d9682698d287d14cbe67a0ed7acc1427add320 (patch) | |
tree | 669ed98d9b1388b553c8e0f0189678cc68dd4162 /devices | |
parent | 460406d10bbfaa890d56d616b4610813da63a312 (diff) | |
parent | 4264464153a7a788ef73c5015ac8bbde5f8ebe1c (diff) | |
download | crosvm-28d9682698d287d14cbe67a0ed7acc1427add320.tar crosvm-28d9682698d287d14cbe67a0ed7acc1427add320.tar.gz crosvm-28d9682698d287d14cbe67a0ed7acc1427add320.tar.bz2 crosvm-28d9682698d287d14cbe67a0ed7acc1427add320.tar.lz crosvm-28d9682698d287d14cbe67a0ed7acc1427add320.tar.xz crosvm-28d9682698d287d14cbe67a0ed7acc1427add320.tar.zst crosvm-28d9682698d287d14cbe67a0ed7acc1427add320.zip |
Merge remote-tracking branch 'origin/master'
Diffstat (limited to 'devices')
24 files changed, 3502 insertions, 168 deletions
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. |