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 <keiichiw@chromium.org>
Tested-by: Keiichi Watanabe <keiichiw@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
Commit-Queue: Keiichi Watanabe <keiichiw@chromium.org>
diff --git a/Cargo.lock b/Cargo.lock
index 4df284d..a696e32 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -183,6 +183,7 @@
"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",
@@ -412,6 +413,15 @@
]
[[package]]
+name = "libvda"
+version = "0.1.0"
+dependencies = [
+ "enumn 0.1.0",
+ "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "linux_input_sys"
version = "0.1.0"
dependencies = [
diff --git a/Cargo.toml b/Cargo.toml
index 3fdad6b..55f7df0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -35,6 +35,8 @@
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 @@
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 @@
[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_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 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::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_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 @@
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..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<Profile>),
+ 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/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<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/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<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..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<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..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<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..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<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 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<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..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<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/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 virtio_input_evdevs: Vec<PathBuf>,
pub split_irqchip: bool,
pub vfio: Vec<PathBuf>,
+ pub video_dec: bool,
+ pub video_enc: bool,
}
impl Default for Config {
@@ -250,6 +252,8 @@
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 @@
})
}
+#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
+fn create_video_device(
+ cfg: &Config,
+ typ: devices::virtio::VideoDeviceType,
+ resource_bridge: virtio::resource_bridge::ResourceRequestSocket,
+) -> DeviceResult {
+ let jail = match simple_jail(&cfg, "video_device")? {
+ Some(mut jail) => {
+ match typ {
+ devices::virtio::VideoDeviceType::Decoder => {
+ add_crosvm_user_to_jail(&mut jail, "video-decoder")?
+ }
+ devices::virtio::VideoDeviceType::Encoder => {
+ add_crosvm_user_to_jail(&mut jail, "video-encoder")?
+ }
+ };
+
+ // Create a tmpfs in the device's root directory so that we can bind mount files.
+ jail.mount_with_data(
+ Path::new("none"),
+ Path::new("/"),
+ "tmpfs",
+ (libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC) as usize,
+ "size=67108864",
+ )?;
+
+ // Render node for libvda.
+ let dev_dri_path = Path::new("/dev/dri/renderD128");
+ jail.mount_bind(dev_dri_path, dev_dri_path, false)?;
+
+ // Device nodes required by libchrome which establishes Mojo connection in libvda.
+ let dev_urandom_path = Path::new("/dev/urandom");
+ jail.mount_bind(dev_urandom_path, dev_urandom_path, false)?;
+ let system_bus_socket_path = Path::new("/run/dbus/system_bus_socket");
+ jail.mount_bind(system_bus_socket_path, system_bus_socket_path, true)?;
+
+ Some(jail)
+ }
+ None => None,
+ };
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(devices::virtio::VideoDevice::new(
+ typ,
+ Some(resource_bridge),
+ )),
+ jail,
+ })
+}
+
+#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
+fn register_video_device(
+ devs: &mut Vec<VirtioDeviceStub>,
+ resource_bridges: &mut Vec<virtio::resource_bridge::ResourceResponseSocket>,
+ cfg: &Config,
+ typ: devices::virtio::VideoDeviceType,
+) -> std::result::Result<(), Error> {
+ let (video_socket, gpu_socket) =
+ virtio::resource_bridge::pair().map_err(Error::CreateSocket)?;
+ resource_bridges.push(gpu_socket);
+ devs.push(create_video_device(cfg, typ, video_socket)?);
+ Ok(())
+}
+
fn create_vhost_vsock_device(cfg: &Config, cid: u64, mem: &GuestMemory) -> DeviceResult {
let dev = virtio::vhost::Vsock::new(cid, mem).map_err(Error::VhostVsockDeviceNew)?;
@@ -1088,6 +1152,30 @@
)?);
}
+ #[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 @@
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 @@
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();