// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! This module implements the virtio video encoder and decoder devices.
//! The current implementation uses [v3 RFC] of the virtio-video protocol.
//!
//! [v3 RFC]: https://markmail.org/thread/wxdne5re7aaugbjg
use std::fmt::{self, Display};
use std::os::unix::io::{AsRawFd, RawFd};
use std::thread;
use data_model::{DataInit, Le32};
use sys_util::{error, Error as SysError, EventFd, GuestMemory};
use crate::virtio::resource_bridge::ResourceRequestSocket;
use crate::virtio::virtio_device::VirtioDevice;
use crate::virtio::{self, copy_config, DescriptorError, Interrupt, VIRTIO_F_VERSION_1};
#[macro_use]
mod macros;
mod command;
mod control;
mod decoder;
mod device;
mod encoder;
mod error;
mod event;
mod format;
mod params;
mod protocol;
mod response;
mod worker;
use command::ReadCmdError;
use device::AsyncCmdTag;
use worker::Worker;
const QUEUE_SIZE: u16 = 256;
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE, QUEUE_SIZE];
/// An error indicating something went wrong in virtio-video's worker.
#[derive(Debug)]
pub enum Error {
/// Failed to create a libvda instance.
LibvdaCreationFailed(libvda::Error),
/// Creating PollContext failed.
PollContextCreationFailed(SysError),
/// A DescriptorChain contains invalid data.
InvalidDescriptorChain(DescriptorError),
/// Invalid output buffer is specified for EOS notification.
InvalidEOSResource { stream_id: u32, resource_id: u32 },
/// No available descriptor in which an event is written to.
DescriptorNotAvailable,
/// Output buffer for EOS is unavailable.
NoEOSBuffer { stream_id: u32 },
/// Error while polling for events.
PollError(SysError),
/// Failed to read a virtio-video command.
ReadFailure(ReadCmdError),
/// Got response for an unexpected asynchronous command.
UnexpectedResponse(AsyncCmdTag),
/// Failed to write an event into the event queue.
WriteEventFailure {
event: event::VideoEvt,
error: std::io::Error,
},
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Error::*;
match self {
LibvdaCreationFailed(e) => write!(f, "failed to create a libvda instance: {}", e),
PollContextCreationFailed(e) => write!(f, "failed to create PollContext: {}", e),
InvalidDescriptorChain(e) => write!(f, "DescriptorChain contains invalid data: {}", e),
InvalidEOSResource {
stream_id,
resource_id,
} => write!(
f,
"invalid resource {} was specified for stream {}'s EOS",
resource_id, stream_id
),
DescriptorNotAvailable => {
write!(f, "no available descriptor in which an event is written to")
}
NoEOSBuffer { stream_id } => write!(
f,
"no output resource is available to notify EOS: {}",
stream_id
),
PollError(err) => write!(f, "failed to poll events: {}", err),
ReadFailure(e) => write!(f, "failed to read a command from the guest: {}", e),
UnexpectedResponse(tag) => {
write!(f, "got a response for an untracked command: {:?}", tag)
}
WriteEventFailure { event, error } => write!(
f,
"failed to write an event {:?} into event queue: {}",
event, error
),
}
}
}
impl std::error::Error for Error {}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub enum VideoDeviceType {
Decoder,
Encoder,
}
pub struct VideoDevice {
device_type: VideoDeviceType,
kill_evt: Option<EventFd>,
resource_bridge: Option<ResourceRequestSocket>,
}
impl VideoDevice {
pub fn new(
device_type: VideoDeviceType,
resource_bridge: Option<ResourceRequestSocket>,
) -> VideoDevice {
VideoDevice {
device_type,
kill_evt: None,
resource_bridge,
}
}
}
impl Drop for VideoDevice {
fn drop(&mut self) {
if let Some(kill_evt) = self.kill_evt.take() {
// Ignore the result because there is nothing we can do about it.
let _ = kill_evt.write(1);
}
}
}
impl VirtioDevice for VideoDevice {
fn keep_fds(&self) -> Vec<RawFd> {
let mut keep_fds = Vec::new();
if let Some(resource_bridge) = &self.resource_bridge {
keep_fds.push(resource_bridge.as_raw_fd());
}
keep_fds
}
fn device_type(&self) -> u32 {
match &self.device_type {
VideoDeviceType::Decoder => virtio::TYPE_VIDEO_DEC,
VideoDeviceType::Encoder => virtio::TYPE_VIDEO_ENC,
}
}
fn queue_max_sizes(&self) -> &[u16] {
QUEUE_SIZES
}
fn features(&self) -> u64 {
1u64 << VIRTIO_F_VERSION_1
| 1u64 << protocol::VIRTIO_VIDEO_F_RESOURCE_NON_CONTIG
| 1u64 << protocol::VIRTIO_VIDEO_F_RESOURCE_VIRTIO_OBJECT
}
fn read_config(&self, offset: u64, data: &mut [u8]) {
let mut cfg = protocol::virtio_video_config {
version: Le32::from(0),
max_caps_length: Le32::from(1024), // Set a big number
max_resp_length: Le32::from(1024), // Set a big number
};
copy_config(data, 0, cfg.as_mut_slice(), offset);
}
fn activate(
&mut self,
mem: GuestMemory,
interrupt: Interrupt,
mut queues: Vec<virtio::queue::Queue>,
mut queue_evts: Vec<EventFd>,
) {
if queues.len() != QUEUE_SIZES.len() {
error!(
"wrong number of queues are passed: expected {}, actual {}",
queues.len(),
QUEUE_SIZES.len()
);
return;
}
if queue_evts.len() != QUEUE_SIZES.len() {
error!(
"wrong number of event FDs are passed: expected {}, actual {}",
queue_evts.len(),
QUEUE_SIZES.len()
);
}
let (self_kill_evt, kill_evt) = match EventFd::new().and_then(|e| Ok((e.try_clone()?, e))) {
Ok(v) => v,
Err(e) => {
error!("failed to create kill EventFd pair: {:?}", e);
return;
}
};
self.kill_evt = Some(self_kill_evt);
let cmd_queue = queues.remove(0);
let cmd_evt = queue_evts.remove(0);
let event_queue = queues.remove(0);
let event_evt = queue_evts.remove(0);
let resource_bridge = match self.resource_bridge.take() {
Some(r) => r,
None => {
error!("no resource bridge is passed");
return;
}
};
let mut worker = Worker {
interrupt,
mem,
cmd_evt,
event_evt,
kill_evt,
resource_bridge,
};
let worker_result = match &self.device_type {
VideoDeviceType::Decoder => thread::Builder::new()
.name("virtio video decoder".to_owned())
.spawn(move || {
let vda = libvda::VdaInstance::new(libvda::VdaImplType::Gavda)
.map_err(Error::LibvdaCreationFailed)?;
let device = decoder::Decoder::new(&vda);
worker.run(cmd_queue, event_queue, device)
}),
VideoDeviceType::Encoder => thread::Builder::new()
.name("virtio video encoder".to_owned())
.spawn(move || {
let device = encoder::Encoder::new();
worker.run(cmd_queue, event_queue, device)
}),
};
if let Err(e) = worker_result {
error!(
"failed to spawn virtio_video worker for {:?}: {}",
&self.device_type, e
);
return;
}
}
}