From 57df6a0ab23c3b2ba233b9aa5886ecf47ba3f91f Mon Sep 17 00:00:00 2001 From: Keiichi Watanabe Date: Fri, 6 Dec 2019 22:24:40 +0900 Subject: devices: virtio: Initial implementation of virtio-video device This CL adds a fundamental part of the virtio video device, which will be shared between the encoder and the decoder. Both devices uses the virtio-video protocol proposed as RFC v3 [1,2]. The corresponding driver code is at CL:2060327 and its children CLs. The actual decoding and encoding logic will be implemented in different CLs. [1]: mail: https://markmail.org/thread/wxdne5re7aaugbjg [2]: PDF: https://drive.google.com/file/d/1jOsS2WdVhL4PpcWLO8Zukq5J0fXDiWn-/view BUG=b:147465619, b:140082257 TEST=cargo check --features=video-decoder,video-encoder TEST=ARCVM started with --video-decoder --video-encoder Cq-Depend: chromium:2203997 Change-Id: I01999eea218ba0f3aaed1558ca2311a57d0c6819 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1973973 Reviewed-by: Keiichi Watanabe Tested-by: Keiichi Watanabe Tested-by: kokoro Commit-Queue: Keiichi Watanabe --- Cargo.lock | 10 + Cargo.toml | 3 + devices/Cargo.toml | 3 + devices/src/virtio/mod.rs | 8 + devices/src/virtio/video/command.rs | 335 ++++++++++++++++++++++ devices/src/virtio/video/control.rs | 83 ++++++ devices/src/virtio/video/decoder/mod.rs | 40 +++ devices/src/virtio/video/device.rs | 93 ++++++ devices/src/virtio/video/encoder/mod.rs | 40 +++ devices/src/virtio/video/error.rs | 84 ++++++ devices/src/virtio/video/event.rs | 36 +++ devices/src/virtio/video/format.rs | 116 ++++++++ devices/src/virtio/video/macros.rs | 46 +++ devices/src/virtio/video/mod.rs | 250 ++++++++++++++++ devices/src/virtio/video/params.rs | 111 ++++++++ devices/src/virtio/video/protocol.rs | 487 ++++++++++++++++++++++++++++++++ devices/src/virtio/video/response.rs | 95 +++++++ devices/src/virtio/video/worker.rs | 362 ++++++++++++++++++++++++ seccomp/x86_64/video_device.policy | 24 ++ src/crosvm.rs | 4 + src/linux.rs | 88 ++++++ src/main.rs | 10 + 22 files changed, 2328 insertions(+) create mode 100644 devices/src/virtio/video/command.rs create mode 100644 devices/src/virtio/video/control.rs create mode 100644 devices/src/virtio/video/decoder/mod.rs create mode 100644 devices/src/virtio/video/device.rs create mode 100644 devices/src/virtio/video/encoder/mod.rs create mode 100644 devices/src/virtio/video/error.rs create mode 100644 devices/src/virtio/video/event.rs create mode 100644 devices/src/virtio/video/format.rs create mode 100644 devices/src/virtio/video/macros.rs create mode 100644 devices/src/virtio/video/mod.rs create mode 100644 devices/src/virtio/video/params.rs create mode 100644 devices/src/virtio/video/protocol.rs create mode 100644 devices/src/virtio/video/response.rs create mode 100644 devices/src/virtio/video/worker.rs create mode 100644 seccomp/x86_64/video_device.policy diff --git a/Cargo.lock b/Cargo.lock index 4df284d..a696e32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -183,6 +183,7 @@ dependencies = [ "kvm_sys 0.1.0", "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", "libcras 0.1.0", + "libvda 0.1.0", "linux_input_sys 0.1.0", "msg_on_socket_derive 0.1.0", "msg_socket 0.1.0", @@ -411,6 +412,15 @@ dependencies = [ "sys_util 0.1.0", ] +[[package]] +name = "libvda" +version = "0.1.0" +dependencies = [ + "enumn 0.1.0", + "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "linux_input_sys" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 3fdad6b..55f7df0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,8 @@ default-no-sandbox = [] gpu = ["devices/gpu"] plugin = ["protos/plugin", "crosvm_plugin", "protobuf"] tpm = ["devices/tpm"] +video-decoder = ["devices/video-decoder"] +video-encoder = ["devices/video-encoder"] wl-dmabuf = ["devices/wl-dmabuf", "gpu_buffer", "resources/wl-dmabuf"] x = ["devices/x"] virtio-gpu-next = ["gpu_renderer/virtio-gpu-next"] @@ -89,6 +91,7 @@ assertions = { path = "assertions" } audio_streams = { path = "../../third_party/adhd/audio_streams" } # ignored by ebuild data_model = { path = "data_model" } libcras = { path = "../../third_party/adhd/cras/client/libcras" } # ignored by ebuild +libvda = { path = "../../platform2/arc/vm/libvda/rust" } # ignored by ebuild minijail-sys = { path = "../../aosp/external/minijail" } # ignored by ebuild poll_token_derive = { path = "sys_util/poll_token_derive" } sync = { path = "sync" } diff --git a/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/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 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, + uuid: u128, + }, + ResourceQueue { + stream_id: u32, + queue_type: QueueType, + resource_id: u32, + timestamp: u64, + data_sizes: Vec, + }, + 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 { + 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::()?; + + 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::::into(planes_layout) != VIRTIO_VIDEO_PLANES_LAYOUT_SINGLE_BUFFER { + error!( + "each buffer must be a single DMAbuf: {}", + Into::::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::::into(*x)) + .collect::>(); + + 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::::into(*x)) + .collect::>(); + 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::()?; + let query_ctrl_type = match body.control.into() { + VIRTIO_VIDEO_CONTROL_BITRATE => QueryCtrlType::Bitrate, + VIRTIO_VIDEO_CONTROL_PROFILE => QueryCtrlType::Profile( + r.read_obj::()? + .format + .try_into()?, + ), + VIRTIO_VIDEO_CONTROL_LEVEL => QueryCtrlType::Level( + r.read_obj::()? + .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::()? + .bitrate + .into(), + ), + VIRTIO_VIDEO_CONTROL_PROFILE => CtrlVal::Profile( + r.read_obj::()? + .profile + .try_into()?, + ), + VIRTIO_VIDEO_CONTROL_LEVEL => CtrlVal::Level( + r.read_obj::()? + .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..6394dc2 --- /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), +} + +#[allow(dead_code)] // TODO(keiichiw): Remove this attribute +#[derive(Debug)] +pub enum QueryCtrlResponse { + Profile(Vec), + Level(Vec), +} + +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/mod.rs b/devices/src/virtio/video/decoder/mod.rs new file mode 100644 index 0000000..d283a11 --- /dev/null +++ b/devices/src/virtio/video/decoder/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 `Decoder` 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 Decoder; + +impl Decoder { + pub fn new() -> Self { + Decoder {} + } +} + +impl Device for Decoder { + fn process_cmd( + &mut self, + _cmd: VideoCmd, + _poll_ctx: &PollContext, + _resource_bridge: &ResourceRequestSocket, + ) -> VideoResult { + Err(VideoError::InvalidOperation) + } + + fn process_event_fd(&mut self, _stream_id: u32) -> Option { + None + } + + fn take_resource_id_to_notify_eos(&mut self, _stream_id: u32) -> Option { + None + } +} diff --git a/devices/src/virtio/video/device.rs b/devices/src/virtio/video/device.rs new file mode 100644 index 0000000..d3ec1d6 --- /dev/null +++ b/devices/src/virtio/video/device.rs @@ -0,0 +1,93 @@ +// 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. + +#![allow(dead_code)] // TODO(keiichiw): Remove this attribute + +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, + }, + /// 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, + resource_bridge: &ResourceRequestSocket, + ) -> VideoResult; + + /// 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; + + /// 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; +} 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, + _resource_bridge: &ResourceRequestSocket, + ) -> VideoResult { + Err(VideoError::InvalidOperation) + } + + fn process_event_fd(&mut self, _stream_id: u32) -> Option { + None + } + + fn take_resource_id_to_notify_eos(&mut self, _stream_id: u32) -> Option { + None + } +} diff --git a/devices/src/virtio/video/error.rs b/devices/src/virtio/video/error.rs new file mode 100644 index 0000000..1709306 --- /dev/null +++ b/devices/src/virtio/video/error.rs @@ -0,0 +1,84 @@ +// 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)] +#[allow(dead_code)] +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 = Result; + +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..8c49253 --- /dev/null +++ b/devices/src/virtio/video/event.rs @@ -0,0 +1,36 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +//! 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; + +#[allow(dead_code)] // TODO(keiichiw): Remove this attribute +#[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, +} + +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::::into(*r)), + ) + } +} + +#[derive(Debug, Clone)] +pub struct FormatDesc { + pub mask: u64, + pub format: Format, + pub frame_formats: Vec, +} + +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 for an enum that implements `enumn::N`. +#[macro_export] +macro_rules! impl_try_from_le32_for_enumn { + ($ty:ty, $name:literal) => { + impl TryFrom for $ty { + type Error = ReadCmdError; + + fn try_from(x: Le32) -> Result { + 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..295cd8c --- /dev/null +++ b/devices/src/virtio/video/mod.rs @@ -0,0 +1,250 @@ +// 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 { + /// 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 { + 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 = std::result::Result; + +#[derive(Debug)] +pub enum VideoDeviceType { + Decoder, + Encoder, +} + +pub struct VideoDevice { + device_type: VideoDeviceType, + kill_evt: Option, + resource_bridge: Option, +} + +impl VideoDevice { + pub fn new( + device_type: VideoDeviceType, + resource_bridge: Option, + ) -> 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 { + 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, + mut queue_evts: Vec, + ) { + 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 device = decoder::Decoder::new(); + 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` instead of `Format` because an image format may not be determined until + // video decoding is started in the decoder. + pub format: Option, + 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, +} + +impl TryFrom 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 { + let num_planes = Into::::into(num_planes); // as usize; + if num_planes as usize > plane_formats.len() { + error!( + "num_planes must not exceed {} but {}", + plane_formats.len(), + Into::::into(num_planes) + ); + return Err(ReadCmdError::InvalidArgument); + } + let plane_formats = plane_formats[0..num_planes as usize] + .iter() + .map(|x| Into::::into(*x)) + .collect::>(); + + 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::::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..e4b4385 --- /dev/null +++ b/devices/src/virtio/video/response.rs @@ -0,0 +1,95 @@ +// 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_*`. +#[allow(dead_code)] // TODO(keiichiw): Remove this attribute +#[derive(Debug)] +pub enum CmdResponse { + NoData, + QueryCapability(Vec), + 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>; +/// Pair of a descriptor chain and a response to be written. +type WritableResp<'a> = (DescriptorChain<'a>, VideoResult); + +/// 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>) { + 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, + ) -> 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, + mut desc_pool: DescPool<'a>, + desc: DescriptorChain<'a>, + ) -> Result<(DescPool<'a>, VecDeque>)> { + let mut resps: VecDeque = 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::>::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, + mut desc_pool: DescPool<'a>, + ) -> Result> { + 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)> { + let mut responses: VecDeque = 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::>::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> { + 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( + &mut self, + mut cmd_queue: Queue, + mut event_queue: Queue, + mut device: T, + ) -> Result<()> { + let poll_ctx: PollContext = 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/seccomp/x86_64/video_device.policy b/seccomp/x86_64/video_device.policy new file mode 100644 index 0000000..fdc5935 --- /dev/null +++ b/seccomp/x86_64/video_device.policy @@ -0,0 +1,24 @@ +# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +@include /usr/share/policy/crosvm/common_device.policy + +# Syscalls specific to video devices. +clock_getres: 1 +connect: 1 +fcntl: arg1 == F_GETFL || arg1 == F_SETFL || arg1 == F_DUPFD_CLOEXEC || arg1 == F_GETFD || arg1 == F_SETFD +getdents: 1 +getegid: 1 +geteuid: 1 +getgid: 1 +getresgid: 1 +getresuid: 1 +getsockname: 1 +getuid: 1 +# ioctl: arg1 == DRM_IOCTL_* +ioctl: arg1 & 0x6400 +openat: 1 +setpriority: 1 +socket: arg0 == AF_UNIX +stat: 1 diff --git a/src/crosvm.rs b/src/crosvm.rs index 49a08c0..33ed236 100644 --- a/src/crosvm.rs +++ b/src/crosvm.rs @@ -202,6 +202,8 @@ pub struct Config { pub virtio_input_evdevs: Vec, pub split_irqchip: bool, pub vfio: Vec, + pub video_dec: bool, + pub video_enc: bool, } impl Default for Config { @@ -250,6 +252,8 @@ impl Default for Config { virtio_input_evdevs: Vec::new(), split_irqchip: false, vfio: Vec::new(), + video_dec: false, + video_enc: false, } } } diff --git a/src/linux.rs b/src/linux.rs index e480a4c..e95f372 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -792,6 +792,70 @@ fn create_wayland_device( }) } +#[cfg(any(feature = "video-decoder", feature = "video-encoder"))] +fn create_video_device( + cfg: &Config, + typ: devices::virtio::VideoDeviceType, + resource_bridge: virtio::resource_bridge::ResourceRequestSocket, +) -> DeviceResult { + let jail = match simple_jail(&cfg, "video_device")? { + Some(mut jail) => { + match typ { + devices::virtio::VideoDeviceType::Decoder => { + add_crosvm_user_to_jail(&mut jail, "video-decoder")? + } + devices::virtio::VideoDeviceType::Encoder => { + add_crosvm_user_to_jail(&mut jail, "video-encoder")? + } + }; + + // Create a tmpfs in the device's root directory so that we can bind mount files. + jail.mount_with_data( + Path::new("none"), + Path::new("/"), + "tmpfs", + (libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC) as usize, + "size=67108864", + )?; + + // Render node for libvda. + let dev_dri_path = Path::new("/dev/dri/renderD128"); + jail.mount_bind(dev_dri_path, dev_dri_path, false)?; + + // Device nodes required by libchrome which establishes Mojo connection in libvda. + let dev_urandom_path = Path::new("/dev/urandom"); + jail.mount_bind(dev_urandom_path, dev_urandom_path, false)?; + let system_bus_socket_path = Path::new("/run/dbus/system_bus_socket"); + jail.mount_bind(system_bus_socket_path, system_bus_socket_path, true)?; + + Some(jail) + } + None => None, + }; + + Ok(VirtioDeviceStub { + dev: Box::new(devices::virtio::VideoDevice::new( + typ, + Some(resource_bridge), + )), + jail, + }) +} + +#[cfg(any(feature = "video-decoder", feature = "video-encoder"))] +fn register_video_device( + devs: &mut Vec, + resource_bridges: &mut Vec, + cfg: &Config, + typ: devices::virtio::VideoDeviceType, +) -> std::result::Result<(), Error> { + let (video_socket, gpu_socket) = + virtio::resource_bridge::pair().map_err(Error::CreateSocket)?; + resource_bridges.push(gpu_socket); + devs.push(create_video_device(cfg, typ, video_socket)?); + Ok(()) +} + fn create_vhost_vsock_device(cfg: &Config, cid: u64, mem: &GuestMemory) -> DeviceResult { let dev = virtio::vhost::Vsock::new(cid, mem).map_err(Error::VhostVsockDeviceNew)?; @@ -1088,6 +1152,30 @@ fn create_virtio_devices( )?); } + #[cfg(feature = "video-decoder")] + { + if cfg.video_dec { + register_video_device( + &mut devs, + &mut resource_bridges, + cfg, + devices::virtio::VideoDeviceType::Decoder, + )?; + } + } + + #[cfg(feature = "video-encoder")] + { + if cfg.video_enc { + register_video_device( + &mut devs, + &mut resource_bridges, + cfg, + devices::virtio::VideoDeviceType::Encoder, + )?; + } + } + #[cfg(feature = "gpu")] { if let Some(gpu_parameters) = &cfg.gpu_parameters { diff --git a/src/main.rs b/src/main.rs index 557c630..61fcd48 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1226,6 +1226,12 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: cfg.vfio.push(vfio_path); } + "video-decoder" => { + cfg.video_dec = true; + } + "video-encoder" => { + cfg.video_enc = true; + } "help" => return Err(argument::Error::PrintHelp), _ => unreachable!(), @@ -1396,6 +1402,10 @@ writeback=BOOL - Indicates whether the VM can use writeback caching (default: fa Argument::flag("split-irqchip", "(EXPERIMENTAL) enable split-irqchip support"), Argument::value("bios", "PATH", "Path to BIOS/firmware ROM"), Argument::value("vfio", "PATH", "Path to sysfs of pass through or mdev device"), + #[cfg(feature = "video-decoder")] + Argument::flag("video-decoder", "(EXPERIMENTAL) enable virtio-video decoder device"), + #[cfg(feature = "video-encoder")] + Argument::flag("video-encoder", "(EXPERIMENTAL) enable virtio-video encoder device"), Argument::short_flag('h', "help", "Print help message.")]; let mut cfg = Config::default(); -- cgit 1.4.1