video: initial skeleton

Initial skeleton for virtio-video crate.
This crate is based on the v3 of the virtio-video
specs patch[1].

It has a big part of the infrastructure
required, although not all commands are implemented,
and does not have any backend available.

Includes support for async responses to the driver
(through VIDEO_EVENT) to QueueResource messages.

[1] -
https://lists.oasis-open.org/archives/virtio-dev/202002/msg00002.html

Related: #364
Signed-off-by: Albert Esteve <aesteve@redhat.com>
This commit is contained in:
Albert Esteve 2023-06-21 17:02:04 +02:00 committed by Stefano Garzarella
parent 1ccb3b6e58
commit 5edf1dfb7b
16 changed files with 2667 additions and 1 deletions

View File

@ -40,6 +40,7 @@ More information may be found in its [README file](./staging/README.md).
Here is the list of device backends in **staging**:
- [Sound](https://github.com/rust-vmm/vhost-device/blob/main/staging/vhost-device-sound/README.md)
- [Video](https://github.com/rust-vmm/vhost-device/blob/main/staging/vhost-device-video/README.md)
<!--
Template:

86
staging/Cargo.lock generated
View File

@ -250,6 +250,16 @@ dependencies = [
"termcolor",
]
[[package]]
name = "epoll"
version = "4.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74351c3392ea1ff6cd2628e0042d268ac2371cb613252ff383b6dfa50d22fa79"
dependencies = [
"bitflags 2.4.1",
"libc",
]
[[package]]
name = "equivalent"
version = "1.0.1"
@ -312,6 +322,7 @@ dependencies = [
"futures-core",
"futures-task",
"futures-util",
"num_cpus",
]
[[package]]
@ -539,6 +550,37 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "num_enum"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70bf6736f74634d299d00086f02986875b3c2d924781a6a2cb6c201e73da0ceb"
dependencies = [
"num_enum_derive",
]
[[package]]
name = "num_enum_derive"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56ea360eafe1022f7cc56cd7b869ed57330fb2453d0c7831d99b74c65d2f5597"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "once_cell"
version = "1.18.0"
@ -595,6 +637,16 @@ version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]]
name = "proc-macro-crate"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
dependencies = [
"once_cell",
"toml_edit 0.19.15",
]
[[package]]
name = "proc-macro2"
version = "1.0.69"
@ -857,7 +909,7 @@ dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
"toml_edit 0.20.2",
]
[[package]]
@ -869,6 +921,17 @@ dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.19.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [
"indexmap",
"toml_datetime",
"winnow",
]
[[package]]
name = "toml_edit"
version = "0.20.2"
@ -938,6 +1001,27 @@ dependencies = [
"vmm-sys-util",
]
[[package]]
name = "vhost-device-video"
version = "0.1.0"
dependencies = [
"bitflags 2.4.1",
"clap",
"env_logger",
"epoll",
"futures-executor",
"libc",
"log",
"num_enum",
"thiserror",
"vhost",
"vhost-user-backend",
"virtio-bindings",
"virtio-queue",
"vm-memory",
"vmm-sys-util",
]
[[package]]
name = "vhost-user-backend"
version = "0.10.1"

View File

@ -2,4 +2,5 @@
resolver = "2"
members = [
"vhost-device-sound",
"vhost-device-video",
]

View File

@ -0,0 +1,15 @@
# Changelog
## [Unreleased]
### Added
### Changed
### Fixed
### Deprecated
## [0.1.0]
First release

View File

@ -0,0 +1,37 @@
[package]
name = "vhost-device-video"
version = "0.1.0"
authors = ["Albert Esteve <aesteve@redhat.com>"]
description = "A virtio-video device using the vhost-user protocol."
repository = "https://github.com/rust-vmm/vhost-device"
readme = "README.md"
keywords = ["vhost", "video", "virt", "backend"]
license = "Apache-2.0 OR BSD-3-Clause"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
xen = ["vm-memory/xen", "vhost/xen", "vhost-user-backend/xen"]
default = []
[dependencies]
bitflags = "2.3.3"
clap = { version = "4.4", features = ["derive"] }
env_logger = "0.10"
epoll = "4.3"
num_enum = "0.7"
log = "0.4"
libc = "0.2.147"
thiserror = "1.0"
futures-executor = { version = "0.3", features = ["thread-pool"] }
vhost = { version = "0.8", features = ["vhost-user-slave"] }
vhost-user-backend = "0.10"
virtio-bindings = "0.2.1"
virtio-queue = "0.9"
vm-memory = "0.12"
vmm-sys-util = "0.11"
[dev-dependencies]
virtio-queue = { version = "0.9", features = ["test-utils"] }
vm-memory = { version = "0.12", features = ["backend-mmap", "backend-atomic"] }

View File

@ -0,0 +1 @@
../../LICENSE-APACHE

View File

@ -0,0 +1 @@
../../LICENSE-BSD-3-Clause

View File

@ -0,0 +1,14 @@
# vhost-user-video
## Design
## Usage
## Working example
## License
This project is licensed under either of
- [Apache License](http://www.apache.org/licenses/LICENSE-2.0), Version 2.0
- [BSD-3-Clause License](https://opensource.org/licenses/BSD-3-Clause)

View File

@ -0,0 +1,7 @@
edition = "2018"
format_generated_files = false
format_code_in_doc_comments = true
format_strings = true
imports_granularity = "Crate"
group_imports = "StdExternalCrate"
wrap_comments = true

View File

@ -0,0 +1,128 @@
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
mod stream;
mod vhu_video;
mod vhu_video_thread;
mod video;
mod video_backends;
use std::{
path::PathBuf,
sync::{Arc, RwLock},
};
use clap::Parser;
use log::{info, warn};
use thiserror::Error as ThisError;
use vhost::{vhost_user, vhost_user::Listener};
use vhost_user_backend::VhostUserDaemon;
use vhu_video::{BackendType, VuVideoBackend};
use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap};
pub(crate) type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, ThisError)]
pub(crate) enum Error {
#[error("Could not create backend: {0}")]
CouldNotCreateBackend(vhu_video::VuVideoError),
#[error("Could not create daemon: {0}")]
CouldNotCreateDaemon(vhost_user_backend::Error),
#[error("Failed creating listener: {0}")]
FailedCreatingListener(vhost_user::Error),
}
#[derive(Clone, Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct VideoArgs {
/// Unix socket to which a hypervisor connects to and sets up the control
/// path with the device.
#[clap(short, long)]
socket_path: PathBuf,
/// Path to the video device file. Defaults to /dev/video0.
#[clap(short = 'd', long, default_value = "/dev/video0")]
v4l2_device: PathBuf,
/// Video backend to be used.
#[clap(short, long)]
#[clap(value_enum)]
backend: BackendType,
}
#[derive(Debug, Eq, PartialEq)]
pub(crate) struct VuVideoConfig {
pub socket_path: PathBuf,
pub v4l2_device: PathBuf,
pub backend: BackendType,
}
impl From<VideoArgs> for VuVideoConfig {
fn from(args: VideoArgs) -> Self {
// Divide available bandwidth by the number of threads in order
// to avoid overwhelming the HW.
Self {
socket_path: args.socket_path.to_owned(),
v4l2_device: args.v4l2_device.to_owned(),
backend: args.backend,
}
}
}
pub(crate) fn start_backend(config: VuVideoConfig) -> Result<()> {
loop {
info!("Starting backend");
let vu_video_backend = Arc::new(RwLock::new(
VuVideoBackend::new(config.v4l2_device.as_path(), config.backend.to_owned())
.map_err(Error::CouldNotCreateBackend)?,
));
let mut daemon = VhostUserDaemon::new(
String::from("vhost-device-video"),
vu_video_backend.clone(),
GuestMemoryAtomic::new(GuestMemoryMmap::new()),
)
.map_err(Error::CouldNotCreateDaemon)?;
let mut vring_workers = daemon.get_epoll_handlers();
for thread in vu_video_backend.read().unwrap().threads.iter() {
thread
.lock()
.unwrap()
.set_vring_workers(vring_workers.remove(0));
}
daemon
.start(Listener::new(&config.socket_path, true).map_err(Error::FailedCreatingListener)?)
.expect("Stargin daemon");
match daemon.wait() {
Ok(()) => {
info!("Stopping cleanly");
}
Err(vhost_user_backend::Error::HandleRequest(
vhost_user::Error::PartialMessage | vhost_user::Error::Disconnected,
)) => {
info!(
"vhost-user connection closed with partial message.
If the VM is shutting down, this is expected behavior;
otherwise, it might be a bug."
);
}
Err(e) => {
warn!("Error running daemon: {:?} -> {}", e, e.to_string());
}
}
vu_video_backend
.read()
.unwrap()
.exit_event
.write(1)
.expect("Shutting down worker thread");
}
}
fn main() -> Result<()> {
env_logger::init();
start_backend(VuVideoConfig::try_from(VideoArgs::parse()).unwrap())
}

View File

@ -0,0 +1,373 @@
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
// Null Backend does not use all stream capabilities
#![cfg_attr(not(any(feature)), allow(dead_code))]
use std::{
collections::HashMap,
fs::File,
future::Future,
os::fd::AsRawFd,
path::Path,
pin::Pin,
sync::{
atomic::{AtomicUsize, Ordering},
Arc, RwLock,
},
task::{Context, Poll, Waker},
};
use num_enum::TryFromPrimitive;
use crate::{
vhu_video::{Result, VuVideoError},
video::{BufferFlags, Format, MemoryType, QueueType},
};
#[repr(C)]
#[derive(Clone, Debug, Default)]
pub struct ResourcePlane {
pub offset: u32,
pub address: u64,
pub length: u32,
}
#[repr(u32)]
#[derive(Clone, Debug, Default, TryFromPrimitive, Eq, PartialEq)]
pub enum ResourceState {
#[default]
Created = 1,
Queried,
Queued,
Ready,
}
#[derive(Clone, Debug, Default)]
pub struct SharedResourceState {
value: ResourceState,
waker: Option<Waker>,
}
impl SharedResourceState {
pub fn is_ready(&self) -> bool {
matches!(self.value, ResourceState::Ready)
}
pub fn set_queried(&mut self) {
self.set_state(ResourceState::Queried);
}
pub fn set_queued(&mut self) {
self.set_state(ResourceState::Queued);
}
pub fn set_ready(&mut self) {
self.set_state(ResourceState::Ready);
}
fn set_state(&mut self, state: ResourceState) {
self.value = state;
}
}
#[derive(Clone, Debug, Default)]
pub struct BufferData {
pub timestamp: u64,
pub flags: BufferFlags,
pub size: u32,
}
impl BufferData {
pub fn set_data(&mut self, flags: BufferFlags, size: u32) {
self.flags = flags;
self.size = size;
}
}
#[repr(C)]
#[derive(Clone, Debug, Default)]
pub struct Resource {
pub stream_id: u32,
pub resource_id: u32,
state: Arc<RwLock<SharedResourceState>>,
pub index: u32,
pub queue_type: QueueType,
pub buffer_data: Arc<RwLock<BufferData>>,
pub planes_layout: u32,
pub planes: Vec<ResourcePlane>,
}
impl Future for Resource {
type Output = BufferData;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.is_ready() {
return Poll::Ready(self.buffer_data.read().unwrap().clone());
} else {
self.state.write().unwrap().waker = Some(cx.waker().clone());
Poll::Pending
}
}
}
impl Resource {
pub fn state(&self) -> ResourceState {
self.state.read().unwrap().value.clone()
}
pub fn ready_with(&mut self, flags: BufferFlags, size: u32) {
self.buffer_data.write().unwrap().set_data(flags, size);
self.set_ready();
}
pub fn set_ready(&mut self) {
self.state.write().unwrap().set_ready();
if let Some(waker) = self.waker().take() {
waker.wake();
}
}
pub fn is_ready(&self) -> bool {
self.state.read().unwrap().is_ready()
}
pub fn set_queried(&mut self) {
self.state.write().unwrap().set_queried();
}
pub fn set_queued(&mut self) {
self.state.write().unwrap().set_queued();
}
pub fn waker(&self) -> Option<Waker> {
self.state.read().unwrap().waker.clone()
}
}
#[repr(u32)]
#[derive(Clone, Debug, Default, TryFromPrimitive, Eq, PartialEq)]
pub enum StreamState {
#[default]
Stopped = 1,
Subscribed,
Streaming,
Draining,
Destroying,
Destroyed,
}
#[derive(Clone, Debug)]
pub struct AtomicStreamState {
state: Arc<AtomicUsize>,
}
impl AtomicStreamState {
pub fn new(state: StreamState) -> Self {
Self {
state: Arc::new(AtomicUsize::new(state as usize)),
}
}
pub fn state(&self) -> StreamState {
StreamState::try_from_primitive(self.state.load(Ordering::SeqCst) as u32)
.expect("Unexpected Stream state")
}
pub fn set_state(&mut self, state: StreamState) {
self.state.store(state as usize, Ordering::SeqCst)
}
}
#[derive(Default, Debug, Clone)]
pub struct ResourcesMap {
pub map: HashMap<u32, Resource>,
pub streaming: bool,
}
impl ResourcesMap {
fn find_mut_by_index(&mut self, v4l2_index: u32) -> Option<&mut Resource> {
self.map
.iter_mut()
.find_map(|(_k, v)| if v.index == v4l2_index { Some(v) } else { None })
}
fn resources(&self) -> Vec<&Resource> {
self.map.values().collect()
}
fn resources_mut(&mut self) -> Vec<&mut Resource> {
self.map.values_mut().collect()
}
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct Stream {
pub stream_id: u32,
pub file: Arc<File>,
state: AtomicStreamState,
pub in_memory_type: MemoryType,
pub out_memory_type: MemoryType,
pub coded_format: Format,
pub inputq_resources: ResourcesMap,
pub outputq_resources: ResourcesMap,
}
impl AsRawFd for Stream {
fn as_raw_fd(&self) -> i32 {
self.file.as_raw_fd()
}
}
impl Stream {
pub(crate) fn new(
stream_id: u32,
device_path: &Path,
in_memory_type: u32,
out_memory_type: u32,
coded_format: u32,
) -> Result<Self> {
Ok(Self {
stream_id,
file: Arc::new(File::create(device_path).unwrap()),
state: AtomicStreamState::new(StreamState::default()),
in_memory_type: MemoryType::try_from_primitive(in_memory_type)
.map_err(|_| VuVideoError::VideoStreamCreate)?,
out_memory_type: MemoryType::try_from_primitive(out_memory_type)
.map_err(|_| VuVideoError::VideoStreamCreate)?,
coded_format: Format::try_from_primitive(coded_format)
.map_err(|_| VuVideoError::VideoStreamCreate)?,
inputq_resources: Default::default(),
outputq_resources: Default::default(),
})
}
pub fn memory(&self, queue_type: QueueType) -> MemoryType {
match queue_type {
QueueType::InputQueue => self.in_memory_type,
QueueType::OutputQueue => self.out_memory_type,
}
}
pub fn find_resource(&self, resource_id: u32, queue_type: QueueType) -> Option<&Resource> {
match queue_type {
QueueType::InputQueue => self.inputq_resources.map.get(&resource_id),
QueueType::OutputQueue => self.outputq_resources.map.get(&resource_id),
}
}
pub fn find_resource_mut_by_index(
&mut self,
v4l2_index: u32,
queue_type: QueueType,
) -> Option<&mut Resource> {
match queue_type {
QueueType::InputQueue => self.inputq_resources.find_mut_by_index(v4l2_index),
QueueType::OutputQueue => self.outputq_resources.find_mut_by_index(v4l2_index),
}
}
pub fn find_resource_mut(
&mut self,
resource_id: u32,
queue_type: QueueType,
) -> Option<&mut Resource> {
match queue_type {
QueueType::InputQueue => self.inputq_resources.map.get_mut(&resource_id),
QueueType::OutputQueue => self.outputq_resources.map.get_mut(&resource_id),
}
}
pub fn is_queue_streaming(&self, queue_type: QueueType) -> bool {
match queue_type {
QueueType::InputQueue => self.inputq_resources.streaming,
QueueType::OutputQueue => self.outputq_resources.streaming,
}
}
pub fn set_queue_streaming(&mut self, queue_type: QueueType) {
match queue_type {
QueueType::InputQueue => self.inputq_resources.streaming = true,
QueueType::OutputQueue => self.outputq_resources.streaming = true,
}
}
pub fn set_state(&mut self, state: StreamState) {
self.state.set_state(state);
}
pub fn state(&self) -> StreamState {
self.state.state()
}
pub fn all_created(&self, queue_type: QueueType) -> bool {
self.all_resources_state(queue_type, ResourceState::Created)
}
fn all_resources_state(&self, queue_type: QueueType, state: ResourceState) -> bool {
match queue_type {
QueueType::InputQueue => self
.inputq_resources
.resources()
.into_iter()
.all(|x| x.state() == state),
QueueType::OutputQueue => self
.outputq_resources
.resources()
.into_iter()
.all(|x| x.state() == state),
}
}
pub fn resources_mut(&mut self, queue_type: QueueType) -> Vec<&mut Resource> {
match queue_type {
QueueType::InputQueue => self.inputq_resources.resources_mut(),
QueueType::OutputQueue => self.outputq_resources.resources_mut(),
}
}
pub fn queued_resources_mut(&mut self, queue_type: QueueType) -> Vec<&mut Resource> {
self.resources_mut(queue_type)
.into_iter()
.filter(|x| (*x).state() == ResourceState::Queued)
.collect()
}
/// Inserts a new resource into the specific queue map.
/// If the map did not have this resource ID present, None is returned.
/// If the map did have this resource ID present, the value is updated, and
/// the old value is returned. The ID is not updated.
pub fn add_resource(
&mut self,
resource_id: u32,
planes_layout: u32,
planes: Vec<ResourcePlane>,
queue_type: QueueType,
) -> Option<Resource> {
let mut resource = Resource {
stream_id: self.stream_id,
resource_id,
queue_type,
planes_layout,
..Default::default()
};
resource.planes = planes;
match queue_type {
QueueType::InputQueue => {
resource.index = self.inputq_resources.map.len() as u32;
self.inputq_resources.map.insert(resource_id, resource)
}
QueueType::OutputQueue => {
resource.index = self.outputq_resources.map.len() as u32;
self.outputq_resources.map.insert(resource_id, resource)
}
}
}
pub fn empty_resources(&mut self, queue_type: QueueType) {
match queue_type {
QueueType::InputQueue => self.inputq_resources.map.clear(),
QueueType::OutputQueue => self.outputq_resources.map.clear(),
}
}
}

View File

@ -0,0 +1,260 @@
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
use std::{
convert,
io::{self, Result as IoResult},
path::Path,
sync::{Arc, Mutex, RwLock},
};
use clap::ValueEnum;
use log::{debug, warn};
use thiserror::Error as ThisError;
use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
use vhost_user_backend::{VhostUserBackendMut, VringRwLock, VringT};
use virtio_bindings::bindings::{
virtio_config::{VIRTIO_F_NOTIFY_ON_EMPTY, VIRTIO_F_VERSION_1},
virtio_ring::{VIRTIO_RING_F_EVENT_IDX, VIRTIO_RING_F_INDIRECT_DESC},
};
use virtio_queue::DescriptorChain;
use vm_memory::{ByteValued, GuestMemoryAtomic, GuestMemoryLoadGuard, GuestMemoryMmap, Le32};
use vmm_sys_util::{
epoll::EventSet,
eventfd::{EventFd, EFD_NONBLOCK},
};
use crate::{vhu_video_thread::VhostUserVideoThread, video_backends};
/// Virtio Video Feature bits
const VIRTIO_VIDEO_F_RESOURCE_GUEST_PAGES: u16 = 0;
/// Unsupported
/// const VIRTIO_VIDEO_F_RESOURCE_NON_CONTIG: u16 = 1;
/// const VIRTIO_VIDEO_F_RESOURCE_VIRTIO_OBJECT: u16 = 2;
const COMMAND_Q: u16 = 0;
const EVENT_Q: u16 = 1;
const NUM_QUEUES: usize = 2;
const QUEUE_SIZE: usize = 1024;
/// Notification coming from the backend.
/// Event range [0...num_queues] is reserved for queues and exit event.
/// So NUM_QUEUES + 1 is used.
pub(crate) const VIDEO_EVENT: u16 = (NUM_QUEUES + 1) as u16;
pub(crate) const VIRTIO_V4L2_CARD_NAME_LEN: usize = 32;
const MAX_CAPS_LEN: u32 = 4096;
const MAX_RESP_LEN: u32 = MAX_CAPS_LEN;
pub(crate) type Result<T> = std::result::Result<T, VuVideoError>;
pub(crate) type VideoDescriptorChain = DescriptorChain<GuestMemoryLoadGuard<GuestMemoryMmap<()>>>;
#[derive(Debug, ThisError)]
/// Errors related to vhost-device-rng daemon.
pub(crate) enum VuVideoError {
#[error("Descriptor not found")]
DescriptorNotFound,
#[error("Descriptor read failed")]
DescriptorReadFailed,
#[error("Notification send failed")]
SendNotificationFailed,
#[error("Can't create eventFd")]
EventFdError,
#[error("Video device file doesn't exists or can't be accessed")]
AccessVideoDeviceFile,
#[error("Failed to create stream")]
VideoStreamCreate,
#[error("Failed to handle event")]
HandleEventNotEpollIn,
#[error("Unknown device event")]
HandleUnknownEvent,
#[error("Invalid command type {0}")]
InvalidCmdType(u32),
#[error("Invalid Resource ID {0}")]
InvalidResourceId(u32),
#[error("Too many descriptors: {0}")]
UnexpectedDescriptorCount(usize),
#[error("Invalid descriptor size, expected at least: {0}, found: {1}")]
UnexpectedMinimumDescriptorSize(usize, usize),
#[error("Invalid descriptor size, expected: {0}, found: {1}")]
UnexpectedDescriptorSize(usize, usize),
#[error("Received unexpected readable descriptor at index {0}")]
UnexpectedReadableDescriptor(usize),
#[error("Invalid value for argument: {0}")]
UnexpectedArgValue(String),
#[error("Failed to create an epoll fd: {0}")]
EpollFdCreate(std::io::Error),
#[error("Failed to add to epoll: {0}")]
EpollAdd(#[from] std::io::Error),
#[error("Failed to modify evset associated with epoll: {0}")]
EpollModify(std::io::Error),
#[error("Failed to consume new epoll event: {0}")]
EpollWait(std::io::Error),
#[error("Failed to de-register fd from epoll: {0}")]
EpollRemove(std::io::Error),
#[error("No memory configured")]
NoMemoryConfigured,
#[error("Unable to create thread pool: {0}")]
CreateThreadPool(std::io::Error),
}
impl convert::From<VuVideoError> for io::Error {
fn from(e: VuVideoError) -> Self {
io::Error::new(io::ErrorKind::Other, e)
}
}
#[derive(ValueEnum, Debug, Default, Clone, Eq, PartialEq)]
pub(crate) enum BackendType {
#[default]
Null,
}
/// Virtio Video Configuration
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct VirtioVideoConfig {
version: Le32,
max_caps_length: Le32,
max_resp_length: Le32,
device_name: [u8; VIRTIO_V4L2_CARD_NAME_LEN],
}
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
unsafe impl ByteValued for VirtioVideoConfig {}
pub(crate) struct VuVideoBackend {
config: VirtioVideoConfig,
pub threads: Vec<Mutex<VhostUserVideoThread>>,
pub exit_event: EventFd,
}
impl VuVideoBackend {
/// Create a new virtio video device for /dev/video<num>.
pub fn new(video_path: &Path, video_backend: BackendType) -> Result<Self> {
let backend = Arc::new(RwLock::new(video_backends::alloc_video_backend(
video_backend,
video_path,
)?));
Ok(Self {
config: VirtioVideoConfig {
version: 0.into(),
max_caps_length: MAX_CAPS_LEN.into(),
max_resp_length: MAX_RESP_LEN.into(),
device_name: [0; 32],
},
threads: vec![Mutex::new(VhostUserVideoThread::new(backend.clone())?)],
exit_event: EventFd::new(EFD_NONBLOCK).map_err(|_| VuVideoError::EventFdError)?,
})
}
}
/// VhostUserBackend trait methods
impl VhostUserBackendMut<VringRwLock, ()> for VuVideoBackend {
fn num_queues(&self) -> usize {
NUM_QUEUES
}
fn max_queue_size(&self) -> usize {
QUEUE_SIZE
}
fn features(&self) -> u64 {
debug!("Get features");
1 << VIRTIO_F_VERSION_1
| 1 << VIRTIO_F_NOTIFY_ON_EMPTY
| 1 << VIRTIO_RING_F_INDIRECT_DESC
| 1 << VIRTIO_RING_F_EVENT_IDX
| 1 << VIRTIO_VIDEO_F_RESOURCE_GUEST_PAGES
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
}
fn protocol_features(&self) -> VhostUserProtocolFeatures {
debug!("Get protocol features");
VhostUserProtocolFeatures::MQ | VhostUserProtocolFeatures::CONFIG
}
fn set_event_idx(&mut self, enabled: bool) {
for thread in self.threads.iter() {
thread.lock().unwrap().event_idx = enabled;
}
}
fn update_memory(&mut self, mem: GuestMemoryAtomic<GuestMemoryMmap>) -> IoResult<()> {
for thread in self.threads.iter() {
thread.lock().unwrap().mem = Some(mem.clone());
}
Ok(())
}
fn handle_event(
&mut self,
device_event: u16,
evset: EventSet,
vrings: &[VringRwLock],
thread_id: usize,
) -> IoResult<bool> {
if evset != EventSet::IN {
return Err(VuVideoError::HandleEventNotEpollIn.into());
}
let mut thread = self.threads[thread_id].lock().unwrap();
let commandq = &vrings[COMMAND_Q as usize];
let eventq = &vrings[EVENT_Q as usize];
let evt_idx = thread.event_idx;
match device_event {
COMMAND_Q => {
if evt_idx {
// vm-virtio's Queue implementation only checks avail_index
// once, so to properly support EVENT_IDX we need to keep
// calling process_queue() until it stops finding new
// requests on the queue.
loop {
commandq.disable_notification().unwrap();
thread.process_command_queue(commandq)?;
if !commandq.enable_notification().unwrap() {
break;
}
}
} else {
// Without EVENT_IDX, a single call is enough.
thread.process_command_queue(commandq)?;
}
}
EVENT_Q => {
// This queue is used by the device to asynchronously send
// event notifications to the driver. Thus, we do not handle
// incoming events.
warn!("Unexpected event notification received");
}
VIDEO_EVENT => {
thread.process_video_event(eventq)?;
}
_ => {
warn!("unhandled device_event: {}", device_event);
return Err(VuVideoError::HandleUnknownEvent.into());
}
}
Ok(false)
}
fn get_config(&self, _offset: u32, _size: u32) -> Vec<u8> {
let offset = _offset as usize;
let size = _size as usize;
let buf = self.config.as_slice();
if offset + size > buf.len() {
return Vec::new();
}
buf[offset..offset + size].to_vec()
}
fn exit_event(&self, _thread_index: usize) -> Option<EventFd> {
self.exit_event.try_clone().ok()
}
}

View File

@ -0,0 +1,573 @@
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
use std::{
fs::File,
mem::size_of,
ops::Deref,
os::unix::io::{AsRawFd, FromRawFd},
sync::{Arc, RwLock},
};
use futures_executor::{ThreadPool, ThreadPoolBuilder};
use log::{debug, warn};
use vhost_user_backend::{VringEpollHandler, VringRwLock, VringT};
use virtio_queue::{Descriptor, QueueOwnedT};
use vm_memory::{
ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
GuestMemoryMmap,
};
use vmm_sys_util::epoll::EventSet;
use crate::{
stream,
vhu_video::{self, Result, VideoDescriptorChain, VuVideoBackend, VuVideoError},
video::{self, ToBytes},
video_backends::VideoBackend,
};
type ArcVhostBknd = Arc<RwLock<VuVideoBackend>>;
const MAX_BUFFERS: usize = 32;
#[derive(Copy, Clone, Debug)]
pub struct TriggeredEvent {
pub have_read: bool,
pub have_write: bool,
pub have_event: bool,
pub data: u64,
}
pub enum EventType {
None = 0,
Read,
Write,
ReadWrite,
Event,
EventRead,
All,
}
impl From<EventType> for epoll::Events {
fn from(et: EventType) -> epoll::Events {
match et {
EventType::None => epoll::Events::EPOLLERR,
EventType::Read => epoll::Events::EPOLLIN,
EventType::Write => epoll::Events::EPOLLOUT,
EventType::ReadWrite => epoll::Events::EPOLLIN | epoll::Events::EPOLLOUT,
EventType::Event => epoll::Events::EPOLLPRI,
EventType::EventRead => epoll::Events::EPOLLPRI | epoll::Events::EPOLLIN,
EventType::All => {
epoll::Events::EPOLLPRI | epoll::Events::EPOLLIN | epoll::Events::EPOLLOUT
}
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct PollerEvent {
pub event: epoll::Event,
}
impl PollerEvent {
pub fn new(et: EventType, token: u64) -> Self {
Self {
event: epoll::Event::new(epoll::Events::from(et), token),
}
}
}
impl Default for PollerEvent {
fn default() -> Self {
Self::new(EventType::None, 0)
}
}
struct VideoPoller {
/// epoll fd to which new host connections are added.
epoll_file: File,
}
impl VideoPoller {
fn new() -> Result<Self> {
let poll_fd = epoll::create(true).map_err(VuVideoError::EpollFdCreate)?;
Ok(Self {
// SAFETY: Safe as the fd is guaranteed to be valid here.
epoll_file: unsafe { File::from_raw_fd(poll_fd) },
})
}
pub fn add(&self, fd: i32, event: PollerEvent) -> Result<()> {
self.epoll_ctl(epoll::ControlOptions::EPOLL_CTL_ADD, fd, &event)
.map_err(VuVideoError::EpollAdd)?;
Ok(())
}
#[allow(dead_code)]
pub fn modify(&self, fd: i32, event: PollerEvent) -> Result<()> {
self.epoll_ctl(epoll::ControlOptions::EPOLL_CTL_MOD, fd, &event)
.map_err(VuVideoError::EpollModify)?;
Ok(())
}
pub fn remove(&self, fd: i32) -> Result<()> {
self.epoll_ctl(epoll::ControlOptions::EPOLL_CTL_DEL, fd, None)
.map_err(VuVideoError::EpollRemove)?;
Ok(())
}
pub fn wait(&self, events: &mut [PollerEvent], timeout: i32) -> Result<Vec<TriggeredEvent>> {
let mut events: Vec<epoll::Event> = events.iter_mut().map(|e| e.event).collect();
match epoll::wait(self.epoll_file.as_raw_fd(), timeout, events.as_mut_slice()) {
Ok(count) => {
let events = events[0..count]
.iter()
.map(|epoll_event| TriggeredEvent {
have_read: epoll_event.events & epoll::Events::EPOLLIN.bits() != 0,
have_write: epoll_event.events & epoll::Events::EPOLLOUT.bits() != 0,
have_event: epoll_event.events & epoll::Events::EPOLLPRI.bits() != 0,
data: epoll_event.data,
})
.collect();
Ok(events)
}
Err(e) => Err(VuVideoError::EpollWait(e)),
}
}
fn epoll_ctl<'a, T>(
&self,
op: epoll::ControlOptions,
fd: i32,
event: T,
) -> std::result::Result<(), std::io::Error>
where
T: Into<Option<&'a PollerEvent>>,
{
let event: Option<&PollerEvent> = event.into();
let ptr = match event.map(|x| x.event as epoll::Event) {
Some(ev) => ev,
None => epoll::Event::new(epoll::Events::empty(), 0),
};
epoll::ctl(self.epoll_file.as_raw_fd(), op, fd, ptr)
}
}
pub(crate) trait ReadObj<T: ByteValued> {
fn read_body(&self, index: usize, equal: bool) -> Result<T>;
}
impl<T: ByteValued> ReadObj<T> for VideoDescriptorChain {
fn read_body(&self, index: usize, equal: bool) -> Result<T> {
let descriptors: Vec<_> = self.clone().collect();
let descriptor = descriptors[index];
let request_size: usize = size_of::<T>();
let to_read = descriptor.len() as usize;
if equal {
if to_read != request_size {
return Err(VuVideoError::UnexpectedDescriptorSize(
request_size,
to_read,
));
}
} else if to_read < request_size {
return Err(VuVideoError::UnexpectedMinimumDescriptorSize(
request_size,
to_read,
));
}
self.memory()
.read_obj::<T>(descriptor.addr())
.map_err(|_| VuVideoError::DescriptorReadFailed)
}
}
pub(crate) struct VhostUserVideoThread {
/// Guest memory map.
pub mem: Option<GuestMemoryAtomic<GuestMemoryMmap>>,
/// VIRTIO_RING_F_EVENT_IDX.
pub event_idx: bool,
poller: VideoPoller,
vring_worker: Option<Arc<VringEpollHandler<ArcVhostBknd, VringRwLock, ()>>>,
backend: Arc<RwLock<Box<dyn VideoBackend + Sync + Send>>>,
/// Thread pool to handle async commands.
pool: ThreadPool,
}
fn write_descriptor_data<T: ToBytes>(
resp: T,
desc_response: &Descriptor,
desc_chain: &VideoDescriptorChain,
vring: &VringRwLock,
) {
if desc_chain
.memory()
.write_slice(resp.to_bytes().as_slice(), desc_response.addr())
.is_err()
{
warn!("Descriptor write failed");
}
if vring
.add_used(desc_chain.head_index(), desc_response.len())
.is_err()
{
warn!("Couldn't return used descriptors to the ring");
}
}
impl VhostUserVideoThread {
pub fn new(backend: Arc<RwLock<Box<dyn VideoBackend + Sync + Send>>>) -> Result<Self> {
Ok(Self {
mem: None,
event_idx: false,
poller: VideoPoller::new()?,
backend,
vring_worker: None,
pool: ThreadPoolBuilder::new()
.pool_size(MAX_BUFFERS)
.create()
.map_err(VuVideoError::CreateThreadPool)?,
})
}
pub fn set_vring_workers(
&mut self,
vring_worker: Arc<VringEpollHandler<ArcVhostBknd, VringRwLock, ()>>,
) {
self.vring_worker = Some(vring_worker);
self.vring_worker
.as_ref()
.unwrap()
.register_listener(
self.poller.epoll_file.as_raw_fd(),
EventSet::IN,
u64::from(vhu_video::VIDEO_EVENT),
)
.unwrap();
}
pub fn process_requests(
&mut self,
requests: Vec<VideoDescriptorChain>,
vring: &VringRwLock,
) -> Result<bool> {
use video::VideoCmd::*;
if requests.is_empty() {
return Ok(true);
}
for desc_chain in requests {
let descriptors: Vec<_> = desc_chain.clone().collect();
debug!("Video request with n descriptors: {}", descriptors.len());
let mut desc_len: usize = 2;
let response: video::CmdResponseType =
match video::VideoCmd::from_descriptor(&desc_chain) {
Err(e) => {
warn!("Reading command failed: {}", e);
video::CmdResponseType::Sync(video::CmdResponse::Error(
video::CmdError::InvalidOperation,
))
}
Ok(cmd) => {
debug!("Received command: {:?}", cmd);
match cmd {
QueryCapability { queue_type } => {
self.backend.read().unwrap().query_capability(queue_type)
}
QueryControl { control, format: _ } => {
self.backend.read().unwrap().query_control(control)
}
StreamCreate {
stream_id,
in_memory_type,
out_memory_type,
coded_format,
} => self.backend.write().unwrap().create_stream(
stream_id,
in_memory_type as u32,
out_memory_type as u32,
coded_format as u32,
),
StreamDestroy { stream_id } => {
self.backend.write().unwrap().destroy_stream(stream_id)
}
StreamDrain { stream_id } => {
self.backend.write().unwrap().drain_stream(stream_id)
}
ResourceCreate {
stream_id,
queue_type,
resource_id,
planes_layout,
plane_offsets,
} => {
desc_len = 3;
let planes = self.collect_planes(&desc_chain, plane_offsets)?;
self.backend.write().unwrap().create_resource(
stream_id,
resource_id,
planes_layout,
planes,
queue_type,
)
}
ResourceQueue {
stream_id,
queue_type,
resource_id,
timestamp,
data_sizes,
} => {
let mut backend = self.backend.write().unwrap();
if let Some(stream) = backend.stream_mut(&stream_id) {
self.set_stream_poller(stream, stream_id as u64);
}
backend.queue_resource(
stream_id,
queue_type,
resource_id,
timestamp,
data_sizes,
)
}
ResourceDestroyAll {
stream_id,
queue_type,
} => self
.backend
.write()
.unwrap()
.destroy_resources(stream_id, queue_type),
QueueClear {
stream_id,
queue_type,
} => self
.backend
.write()
.unwrap()
.clear_queue(stream_id, queue_type),
GetParams {
stream_id,
queue_type,
} => self
.backend
.write()
.unwrap()
.get_params(stream_id, queue_type),
SetParams {
stream_id,
queue_type: _,
params,
} => self.backend.write().unwrap().set_params(stream_id, params),
GetControl {
stream_id: _,
control: _,
} => {
debug!("GET_CONTROL support is not fully handled yet");
video::CmdResponseType::Sync(video::CmdResponse::Error(
video::CmdError::UnsupportedControl,
))
}
}
}
};
debug!("Response: {:?}", response);
if descriptors.len() != desc_len {
return Err(VuVideoError::UnexpectedDescriptorCount(descriptors.len()));
}
let desc_response = &descriptors[desc_len - 1];
if !desc_response.is_write_only() {
return Err(VuVideoError::UnexpectedReadableDescriptor(desc_len - 1));
}
match response {
video::CmdResponseType::Sync(resp) => {
write_descriptor_data(resp, desc_response, &desc_chain, vring);
}
video::CmdResponseType::AsyncQueue {
stream_id,
queue_type,
resource_id,
} => {
let backend = self.backend.read().unwrap();
let stream = backend.stream(&stream_id).unwrap();
let resource = match stream.find_resource(resource_id, queue_type) {
Some(res) => res.clone(),
None => return Err(VuVideoError::InvalidResourceId(resource_id)),
};
let vring = vring.clone();
let desc_response = *desc_response;
self.pool.spawn_ok(async move {
let buf_data = resource.await;
debug!(
"Dequeued resource {} ({:?}) for stream {}",
resource_id, queue_type, stream_id
);
let resp = video::CmdResponse::ResourceQueue {
timestamp: buf_data.timestamp,
flags: buf_data.flags.bits(),
size: buf_data.size,
};
write_descriptor_data(resp, &desc_response, &desc_chain, &vring);
if let Err(e) = vring
.signal_used_queue()
.map_err(|_| VuVideoError::SendNotificationFailed)
{
warn!("{}", e);
}
});
// Avoid signaling the used queue for delayed responses
return Ok(false);
}
}
}
Ok(true)
}
fn collect_planes(
&self,
desc_chain: &VideoDescriptorChain,
plane_offsets: Vec<u32>,
) -> Result<Vec<stream::ResourcePlane>> {
let mem: video::SingleLayoutBuffer = desc_chain.read_body(1, true)?;
// Assumes mplanar with a single plane
let virt_addr = self
.atomic_mem()?
.memory()
.deref()
.get_host_address(GuestAddress(mem.raw_addr()))
.expect("Could not get the host address");
let plane_addrs = vec![virt_addr as u64];
let plane_lengths = vec![mem.raw_len()];
Ok(plane_offsets
.into_iter()
.zip(plane_addrs)
.zip(plane_lengths)
.map(|((offset, address), length)| stream::ResourcePlane {
offset,
address,
length,
})
.collect())
}
fn set_stream_poller(&self, stream: &stream::Stream, data: u64) {
if stream.state() != stream::StreamState::Streaming
&& stream.state() != stream::StreamState::Draining
{
if let Err(e) = self
.poller
.add(stream.as_raw_fd(), PollerEvent::new(EventType::All, data))
{
warn!("Add poller events to stream ID {} failed: {}", data, e);
}
}
}
fn atomic_mem(&self) -> Result<&GuestMemoryAtomic<GuestMemoryMmap>> {
match &self.mem {
Some(m) => Ok(m),
None => Err(VuVideoError::NoMemoryConfigured),
}
}
/// Process the requests in the vring and dispatch replies
pub fn process_command_queue(&mut self, vring: &VringRwLock) -> Result<bool> {
let requests: Vec<_> = vring
.get_mut()
.get_queue_mut()
.iter(self.atomic_mem()?.memory())
.map_err(|_| VuVideoError::DescriptorNotFound)?
.collect();
if self.process_requests(requests, vring)? {
// Send notification once all the requests are processed
vring
.signal_used_queue()
.map_err(|_| VuVideoError::SendNotificationFailed)?;
}
Ok(true)
}
fn process_event(&self, stream_id: u32, eventq: &VringRwLock) -> Result<bool> {
if let Some(event) = self.backend.read().unwrap().dequeue_event(stream_id) {
let desc_chain = eventq
.get_mut()
.get_queue_mut()
.iter(self.atomic_mem()?.memory())
.map_err(|_| VuVideoError::DescriptorNotFound)?
.collect::<Vec<_>>()
.pop();
let desc_chain = match desc_chain {
Some(desc_chain) => desc_chain,
None => {
warn!("No available buffer found in the event queue.");
return Err(VuVideoError::DescriptorNotFound);
}
};
let descriptors: Vec<_> = desc_chain.clone().collect();
if descriptors.len() > 1 {
return Err(VuVideoError::UnexpectedDescriptorCount(descriptors.len()));
}
write_descriptor_data(event, &descriptors[0], &desc_chain, eventq);
eventq
.signal_used_queue()
.map_err(|_| VuVideoError::SendNotificationFailed)?;
}
Ok(true)
}
fn send_dqbuf(&mut self, stream_id: u32, queue_type: video::QueueType) -> Result<bool> {
let dqbuf_data = match self
.backend
.read()
.unwrap()
.dequeue_resource(stream_id, queue_type)
{
Some(buf_data) => buf_data,
None => return Ok(false),
};
let mut backend = self.backend.write().unwrap();
let stream = backend.stream_mut(&stream_id).unwrap();
if dqbuf_data.flags.contains(video::BufferFlags::EOS)
&& stream.state() == stream::StreamState::Draining
{
stream.set_state(stream::StreamState::Stopped);
if let Err(e) = self.poller.remove(stream.as_raw_fd()) {
warn!("{}", e);
}
}
let resource = stream
.find_resource_mut_by_index(dqbuf_data.index, queue_type)
.unwrap();
resource.ready_with(dqbuf_data.flags, dqbuf_data.size);
Ok(true)
}
pub fn process_video_event(&mut self, eventq: &VringRwLock) -> Result<bool> {
let mut epoll_events = vec![PollerEvent::default(); 1024];
let events = self.poller.wait(epoll_events.as_mut_slice(), 0).unwrap();
for event in events {
let stream_id = event.data as u32;
if event.have_event {
self.process_event(stream_id, eventq)?;
}
if event.have_read {
// TODO: Assumes decoder
self.send_dqbuf(stream_id, video::QueueType::OutputQueue)?;
}
if event.have_write {
// TODO: Assumes decoder
self.send_dqbuf(stream_id, video::QueueType::InputQueue)?;
}
}
Ok(true)
}
}

View File

@ -0,0 +1,943 @@
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
#![allow(dead_code)] //TODO: remove
// Struct definitions use the kernel-style naming for consistency
#![allow(non_camel_case_types)]
use bitflags::bitflags;
use num_enum::TryFromPrimitive;
use vm_memory::{ByteValued, Le32, Le64};
use crate::{
vhu_video::{self, VuVideoError},
vhu_video_thread::ReadObj,
};
pub(crate) type StreamId = u32;
pub(crate) trait ToBytes {
fn to_bytes(&self) -> Vec<u8>;
}
/// Virtio specification definitions
/// Virtio Video Command types
pub(crate) const VIRTIO_VIDEO_CMD_QUERY_CAPABILITY: u32 = 0x0100;
pub(crate) const VIRTIO_VIDEO_CMD_STREAM_CREATE: u32 = 0x0101;
pub(crate) const VIRTIO_VIDEO_CMD_STREAM_DESTROY: u32 = 0x0102;
pub(crate) const VIRTIO_VIDEO_CMD_STREAM_DRAIN: u32 = 0x0103;
pub(crate) const VIRTIO_VIDEO_CMD_RESOURCE_CREATE: u32 = 0x0104;
pub(crate) const VIRTIO_VIDEO_CMD_RESOURCE_QUEUE: u32 = 0x0105;
pub(crate) const VIRTIO_VIDEO_CMD_RESOURCE_DESTROY_ALL: u32 = 0x0106;
pub(crate) const VIRTIO_VIDEO_CMD_QUEUE_CLEAR: u32 = 0x0107;
/// GET/SET_PARAMS are being replaced with GET/SET_PARAMS_EXT
pub(crate) const VIRTIO_VIDEO_CMD_GET_PARAMS__UNUSED: u32 = 0x0108;
pub(crate) const VIRTIO_VIDEO_CMD_SET_PARAMS__UNUSED: u32 = 0x0109;
pub(crate) const VIRTIO_VIDEO_CMD_QUERY_CONTROL: u32 = 0x010A;
pub(crate) const VIRTIO_VIDEO_CMD_GET_CONTROL: u32 = 0x010B;
pub(crate) const VIRTIO_VIDEO_CMD_SET_CONTROL: u32 = 0x010C;
pub(crate) const VIRTIO_VIDEO_CMD_GET_PARAMS_EXT: u32 = 0x010D;
pub(crate) const VIRTIO_VIDEO_CMD_SET_PARAMS_EXT: u32 = 0x010E;
/// Virtio Video Response types
pub(crate) const VIRTIO_VIDEO_RESP_OK_NODATA: u32 = 0x0200;
pub(crate) const VIRTIO_VIDEO_RESP_OK_QUERY_CAPABILITY: u32 = 0x0201;
pub(crate) const VIRTIO_VIDEO_RESP_OK_RESOURCE_QUEUE: u32 = 0x0202;
pub(crate) const VIRTIO_VIDEO_RESP_OK_GET_PARAMS: u32 = 0x0203;
pub(crate) const VIRTIO_VIDEO_RESP_OK_QUERY_CONTROL: u32 = 0x0204;
pub(crate) const VIRTIO_VIDEO_RESP_OK_GET_CONTROL: u32 = 0x0205;
pub(crate) const VIRTIO_VIDEO_RESP_ERR_INVALID_OPERATION: u32 = 0x0206;
pub(crate) const VIRTIO_VIDEO_RESP_ERR_OUT_OF_MEMORY: u32 = 0x0207;
pub(crate) const VIRTIO_VIDEO_RESP_ERR_INVALID_STREAM_ID: u32 = 0x0208;
pub(crate) const VIRTIO_VIDEO_RESP_ERR_INVALID_RESOURCE_ID: u32 = 0x0209;
pub(crate) const VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER: u32 = 0x020A;
pub(crate) const VIRTIO_VIDEO_RESP_ERR_UNSUPPORTED_CONTROL: u32 = 0x020B;
#[derive(Debug, Clone)]
pub enum CmdError {
InvalidOperation,
OutOfMemory,
InvalidStreamId,
InvalidResourceId,
InvalidParameter,
UnsupportedControl,
}
#[derive(Debug, Clone)]
pub enum CmdResponse {
OkNoData,
QueryCapability(Vec<virtio_video_format_desc>),
ResourceQueue {
timestamp: u64,
flags: u32,
size: u32,
},
GetParams {
queue_type: QueueType,
params: virtio_video_params,
},
QueryControl(VideoControl),
GetControl(ControlType),
SetControl,
Error(CmdError),
}
impl CmdResponse {
fn cmd_type(&self) -> Le32 {
use CmdResponse::*;
Le32::from(match self {
OkNoData => 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,
SetControl => VIRTIO_VIDEO_RESP_OK_NODATA,
Error(e) => match e {
CmdError::InvalidOperation => VIRTIO_VIDEO_RESP_ERR_INVALID_OPERATION,
CmdError::OutOfMemory => VIRTIO_VIDEO_RESP_ERR_OUT_OF_MEMORY,
CmdError::InvalidStreamId => VIRTIO_VIDEO_RESP_ERR_INVALID_STREAM_ID,
CmdError::InvalidResourceId => VIRTIO_VIDEO_RESP_ERR_INVALID_RESOURCE_ID,
CmdError::InvalidParameter => VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER,
CmdError::UnsupportedControl => VIRTIO_VIDEO_RESP_ERR_UNSUPPORTED_CONTROL,
},
})
}
}
impl ToBytes for CmdResponse {
fn to_bytes(&self) -> Vec<u8> {
use CmdResponse::*;
let mut response_raw: Vec<u8> = Vec::new();
match self {
QueryCapability(descs) => {
let mut response = virtio_video_query_capability_resp::default();
response.hdr.type_ = self.cmd_type();
response.append_descs(descs);
response_raw.append(&mut response.to_bytes());
}
QueryControl(control_type) => {
let mut response = virtio_video_query_control_resp::default();
response.hdr.type_ = self.cmd_type();
response.resp.num = control_type.get_value();
response_raw.extend_from_slice(response.as_slice());
}
GetParams {
queue_type: _,
params,
} => {
let mut response = virtio_video_get_params_resp::default();
response.hdr.type_ = self.cmd_type();
response.params = *params;
response_raw.extend_from_slice(response.as_slice());
}
GetControl(_c) => {
let mut response = virtio_video_get_control_resp::default();
response.hdr.type_ = self.cmd_type();
response_raw.extend_from_slice(response.hdr.as_slice());
}
ResourceQueue {
timestamp,
flags,
size,
} => {
let mut response = virtio_video_resource_queue_resp::default();
response.hdr.type_ = self.cmd_type();
response.timestamp = (*timestamp).into();
response.flags = BufferFlags::from_bits_retain(*flags);
// Only used for encoder
response.size = (*size).into();
response_raw.extend_from_slice(response.as_slice());
}
OkNoData | SetControl | Error(_) => response_raw.extend_from_slice(
virtio_video_cmd_hdr {
type_: self.cmd_type(),
..Default::default()
}
.as_slice(),
),
};
response_raw
}
}
#[derive(Debug, Clone)]
pub enum CmdResponseType {
Sync(CmdResponse),
AsyncQueue {
stream_id: u32,
queue_type: QueueType,
resource_id: u32,
},
}
/// Virtio Video Formats
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
#[repr(u32)]
pub enum Format {
/// Raw formats
Argb8888 = 1,
Bgra8888,
Nv12,
Yuv420,
Yvu420,
/// Coded formats
Mpeg2 = 0x1000,
Mpeg4,
H264,
Hevc,
Vp8,
Vp9,
#[default]
Fwht,
}
/// Virtio Video Controls
#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
#[repr(u32)]
pub enum ControlType {
Bitrate = 1,
Profile,
Level,
ForceKeyframe,
BitrateMode,
BitratePeak,
PrependSpsPpsToIdr,
}
/// Planes layout
pub(crate) const VIRTIO_VIDEO_PLANES_LAYOUT_SINGLE_BUFFER: u32 = 1 << 0;
pub(crate) const VIRTIO_VIDEO_PLANES_LAYOUT_PER_PLANE: u16 = 1 << 1;
/// Queue type
pub(crate) const VIRTIO_VIDEO_QUEUE_TYPE_INPUT: u32 = 0x100;
pub(crate) const VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT: u32 = 0x101;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
#[repr(u32)]
pub enum QueueType {
#[default]
InputQueue = VIRTIO_VIDEO_QUEUE_TYPE_INPUT,
OutputQueue = VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT,
}
/// Memory type
pub(crate) const VIRTIO_VIDEO_MEM_TYPE_GUEST_PAGES: u32 = 0;
pub(crate) const VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT: u32 = 1;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
#[repr(u32)]
pub enum MemoryType {
#[default]
GuestPages = VIRTIO_VIDEO_MEM_TYPE_GUEST_PAGES,
VirtioObject = VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT,
}
pub(crate) const VIRTIO_VIDEO_MAX_PLANES: u8 = 8;
pub(crate) const MAX_FMT_DESCS: u8 = 64;
pub(crate) trait ToVirtio {
fn to_virtio(&self) -> Le32;
}
pub(crate) trait InitFromValue<T>
where
Self: Sized,
{
fn from_value(value: T) -> Option<Self>;
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum VideoCmd {
QueryCapability {
queue_type: QueueType,
},
StreamCreate {
stream_id: StreamId,
in_memory_type: MemoryType,
out_memory_type: MemoryType,
coded_format: Format,
},
StreamDestroy {
stream_id: StreamId,
},
StreamDrain {
stream_id: StreamId,
},
ResourceCreate {
stream_id: StreamId,
queue_type: QueueType,
resource_id: u32,
planes_layout: u32,
plane_offsets: Vec<u32>,
},
ResourceQueue {
stream_id: StreamId,
queue_type: QueueType,
resource_id: u32,
timestamp: u64,
data_sizes: Vec<u32>,
},
ResourceDestroyAll {
stream_id: StreamId,
queue_type: QueueType,
},
QueueClear {
stream_id: StreamId,
queue_type: QueueType,
},
GetParams {
stream_id: StreamId,
queue_type: QueueType,
},
SetParams {
stream_id: StreamId,
queue_type: QueueType,
params: virtio_video_params,
},
QueryControl {
control: ControlType,
format: Format,
},
GetControl {
stream_id: StreamId,
control: ControlType,
},
}
impl VideoCmd {
pub fn from_descriptor(
desc_chain: &vhu_video::VideoDescriptorChain,
) -> vhu_video::Result<Self> {
use self::VideoCmd::*;
macro_rules! read_body {
($a: expr) => {
desc_chain.read_body(0, $a)
};
}
let header: virtio_video_cmd_hdr = read_body!(false)?;
Ok(match header.type_.into() {
VIRTIO_VIDEO_CMD_QUERY_CAPABILITY => {
let body: virtio_video_query_capability = read_body!(true)?;
QueryCapability {
queue_type: QueueType::try_from_primitive(<Le32 as Into<u32>>::into(
body.queue_type,
))
.map_err(|_| {
VuVideoError::UnexpectedArgValue(stringify!(body.queue_type).to_string())
})?,
}
}
VIRTIO_VIDEO_CMD_QUERY_CONTROL => {
let body: virtio_video_query_control = read_body!(true)?;
QueryControl {
control: ControlType::try_from_primitive(<Le32 as Into<u32>>::into(
body.control,
))
.map_err(|_| {
VuVideoError::UnexpectedArgValue(stringify!(body.control).to_string())
})?,
format: Format::try_from_primitive(<Le32 as Into<u32>>::into(body.fmt.format))
.map_err(|_| {
VuVideoError::UnexpectedArgValue(
stringify!(body.fmt.format).to_string(),
)
})?,
}
}
VIRTIO_VIDEO_CMD_STREAM_CREATE => {
let body: virtio_video_stream_create = read_body!(true)?;
StreamCreate {
stream_id: body.hdr.stream_id.into(),
in_memory_type: MemoryType::try_from_primitive(<Le32 as Into<u32>>::into(
body.in_mem_type,
))
.map_err(|_| {
VuVideoError::UnexpectedArgValue(stringify!(body.in_mem_type).to_string())
})?,
out_memory_type: MemoryType::try_from_primitive(<Le32 as Into<u32>>::into(
body.out_mem_type,
))
.map_err(|_| {
VuVideoError::UnexpectedArgValue(stringify!(body.out_mem_type).to_string())
})?,
coded_format: Format::try_from_primitive(<Le32 as Into<u32>>::into(
body.coded_format,
))
.map_err(|_| {
VuVideoError::UnexpectedArgValue(stringify!(body.format).to_string())
})?,
}
}
VIRTIO_VIDEO_CMD_STREAM_DESTROY => {
let body: virtio_video_cmd_hdr = read_body!(true)?;
StreamDestroy {
stream_id: body.stream_id.into(),
}
}
VIRTIO_VIDEO_CMD_STREAM_DRAIN => {
let body: virtio_video_cmd_hdr = read_body!(true)?;
StreamDrain {
stream_id: body.stream_id.into(),
}
}
VIRTIO_VIDEO_CMD_RESOURCE_CREATE => {
let body: virtio_video_resource_create = read_body!(true)?;
ResourceCreate {
stream_id: body.hdr.stream_id.into(),
queue_type: QueueType::try_from_primitive(<Le32 as Into<u32>>::into(
body.queue_type,
))
.map_err(|_| {
VuVideoError::UnexpectedArgValue(stringify!(body.queue_type).to_string())
})?,
resource_id: body.resource_id.into(),
planes_layout: body.planes_layout.into(),
plane_offsets: body.plane_offsets
[0..<Le32 as Into<u32>>::into(body.num_planes) as usize]
.iter()
.map(|x| Into::<u32>::into(*x))
.collect::<Vec<u32>>(),
}
}
VIRTIO_VIDEO_CMD_RESOURCE_QUEUE => {
let body: virtio_video_resource_queue = read_body!(true)?;
ResourceQueue {
stream_id: body.hdr.stream_id.into(),
queue_type: QueueType::try_from_primitive(<Le32 as Into<u32>>::into(
body.queue_type,
))
.map_err(|_| {
VuVideoError::UnexpectedArgValue(stringify!(body.queue_type).to_string())
})?,
resource_id: body.resource_id.into(),
timestamp: body.timestamp.into(),
data_sizes: body.data_sizes
[0..<Le32 as Into<u32>>::into(body.num_data_sizes) as usize]
.iter()
.map(|x| Into::<u32>::into(*x))
.collect::<Vec<u32>>(),
}
}
VIRTIO_VIDEO_CMD_RESOURCE_DESTROY_ALL => {
let body: virtio_video_get_params = read_body!(true)?;
ResourceDestroyAll {
stream_id: body.hdr.stream_id.into(),
queue_type: QueueType::try_from_primitive(<Le32 as Into<u32>>::into(
body.queue_type,
))
.map_err(|_| {
VuVideoError::UnexpectedArgValue(stringify!(body.queue_type).to_string())
})?,
}
}
VIRTIO_VIDEO_CMD_QUEUE_CLEAR => {
let body: virtio_video_get_params = read_body!(true)?;
QueueClear {
stream_id: body.hdr.stream_id.into(),
queue_type: QueueType::try_from_primitive(<Le32 as Into<u32>>::into(
body.queue_type,
))
.map_err(|_| {
VuVideoError::UnexpectedArgValue(stringify!(body.queue_type).to_string())
})?,
}
}
VIRTIO_VIDEO_CMD_GET_PARAMS_EXT => {
let body: virtio_video_get_params = read_body!(true)?;
GetParams {
stream_id: body.hdr.stream_id.into(),
queue_type: QueueType::try_from_primitive(<Le32 as Into<u32>>::into(
body.queue_type,
))
.map_err(|_| {
VuVideoError::UnexpectedArgValue(stringify!(body.queue_type).to_string())
})?,
}
}
VIRTIO_VIDEO_CMD_SET_PARAMS_EXT => {
let body: virtio_video_set_params = read_body!(true)?;
SetParams {
stream_id: body.hdr.stream_id.into(),
queue_type: QueueType::try_from_primitive(<Le32 as Into<u32>>::into(
body.params.queue_type,
))
.map_err(|_| {
VuVideoError::UnexpectedArgValue(stringify!(body.queue_type).to_string())
})?,
params: body.params,
}
}
VIRTIO_VIDEO_CMD_GET_CONTROL => {
let body: virtio_video_get_control = read_body!(true)?;
GetControl {
stream_id: body.hdr.stream_id.into(),
control: ControlType::try_from_primitive(<Le32 as Into<u32>>::into(
body.control,
))
.map_err(|_| {
VuVideoError::UnexpectedArgValue(stringify!(body.control).to_string())
})?,
}
}
_ => return Err(vhu_video::VuVideoError::InvalidCmdType(header.type_.into())),
})
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub(crate) struct virtio_video_cmd_hdr {
pub type_: Le32,
pub stream_id: Le32,
}
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
unsafe impl ByteValued for virtio_video_cmd_hdr {}
/// Requests
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub(crate) struct virtio_video_query_capability {
pub hdr: virtio_video_cmd_hdr,
pub queue_type: Le32,
pub padding: Le32,
}
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
unsafe impl ByteValued for virtio_video_query_capability {}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub(crate) struct virtio_video_query_control_format {
pub format: Le32,
pub padding: Le32,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub(crate) struct virtio_video_query_control {
pub hdr: virtio_video_cmd_hdr,
pub control: Le32,
pub padding: Le32,
pub fmt: virtio_video_query_control_format,
}
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
unsafe impl ByteValued for virtio_video_query_control {}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub(crate) struct virtio_video_stream_create {
pub hdr: virtio_video_cmd_hdr,
pub in_mem_type: Le32,
pub out_mem_type: Le32,
pub coded_format: Le32,
pub padding: u32,
pub tag: [[u8; 32]; 2],
}
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
unsafe impl ByteValued for virtio_video_stream_create {}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub(crate) struct virtio_video_resource_create {
pub hdr: virtio_video_cmd_hdr,
pub queue_type: Le32,
pub resource_id: Le32,
pub planes_layout: Le32,
pub num_planes: Le32,
pub plane_offsets: [Le32; VIRTIO_VIDEO_MAX_PLANES as usize],
pub num_entries: [Le32; VIRTIO_VIDEO_MAX_PLANES as usize],
}
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
unsafe impl ByteValued for virtio_video_resource_create {}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub(crate) struct virtio_video_resource_queue {
pub hdr: virtio_video_cmd_hdr,
pub queue_type: Le32,
pub resource_id: Le32,
pub timestamp: Le64,
pub num_data_sizes: Le32,
pub data_sizes: [Le32; VIRTIO_VIDEO_MAX_PLANES as usize],
pub padding: Le32,
}
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
unsafe impl ByteValued for virtio_video_resource_queue {}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub(crate) struct virtio_video_get_params {
pub hdr: virtio_video_cmd_hdr,
pub queue_type: Le32,
pub padding: Le32,
}
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
unsafe impl ByteValued for virtio_video_get_params {}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub(crate) struct virtio_video_set_params {
pub hdr: virtio_video_cmd_hdr,
pub params: virtio_video_params,
}
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
unsafe impl ByteValued for virtio_video_set_params {}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub(crate) struct virtio_video_get_control {
pub hdr: virtio_video_cmd_hdr,
pub control: Le32,
pub padding: Le32,
}
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
unsafe impl ByteValued for virtio_video_get_control {}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub struct virtio_video_format_range {
pub min: Le32,
pub max: Le32,
pub step: Le32,
pub padding: Le32,
}
impl From<u32> for virtio_video_format_range {
fn from(range: u32) -> Self {
Self {
min: range.into(),
max: range.into(),
step: 0.into(),
padding: 0.into(),
}
}
}
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
unsafe impl ByteValued for virtio_video_format_range {}
#[repr(C)]
#[derive(Clone, Debug, Default)]
pub struct virtio_video_format_frame {
pub width: virtio_video_format_range,
pub length: virtio_video_format_range,
pub num_rates: Le32,
pub padding: Le32,
pub frame_rates: Vec<virtio_video_format_range>,
}
impl virtio_video_format_frame {
pub fn append_frame_rates(&mut self, frames: &mut Vec<virtio_video_format_range>) {
self.frame_rates.append(frames);
self.num_rates = (self.frame_rates.len() as u32).into();
}
}
impl ToBytes for virtio_video_format_frame {
fn to_bytes(&self) -> Vec<u8> {
let mut ret = [
self.width.as_slice(),
self.length.as_slice(),
self.num_rates.as_slice(),
self.padding.as_slice(),
]
.concat();
self.frame_rates
.iter()
.for_each(|frame_rate| ret.extend_from_slice(frame_rate.as_slice()));
ret
}
}
#[repr(C)]
#[derive(Clone, Debug, Default)]
pub struct virtio_video_format_desc {
mask: Le64,
pub format: Le32,
pub planes_layout: Le32,
plane_align: Le32,
num_frames: Le32,
frames: Vec<virtio_video_format_frame>,
}
impl virtio_video_format_desc {
pub fn append_frames(&mut self, frames: &mut Vec<virtio_video_format_frame>) {
self.frames.append(frames);
self.num_frames = (self.frames.len() as u32).into();
}
pub fn generate_mask(&mut self, num_formats: usize) {
self.mask = (u64::MAX >> (64 - num_formats)).into();
}
}
impl ToBytes for virtio_video_format_desc {
fn to_bytes(&self) -> Vec<u8> {
let mut ret = [
self.mask.as_slice(),
self.format.as_slice(),
self.planes_layout.as_slice(),
self.plane_align.as_slice(),
self.num_frames.as_slice(),
]
.concat();
self.frames
.iter()
.for_each(|frames| ret.append(&mut frames.to_bytes()));
ret
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct virtio_video_plane_format {
pub plane_size: Le32,
pub stride: Le32,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct virtio_video_crop {
pub left: Le32,
pub top: Le32,
pub width: Le32,
pub heigth: Le32,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct virtio_video_params {
pub queue_type: Le32,
pub format: Le32,
pub frame_width: Le32,
pub frame_heigth: 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; VIRTIO_VIDEO_MAX_PLANES as usize],
pub resource_type: Le32,
pub padding: Le32,
}
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
unsafe impl ByteValued for virtio_video_params {}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub struct virtio_video_mem_entry {
pub addr: Le64,
pub length: Le32,
pub padding: Le32,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub struct SingleLayoutBuffer(pub virtio_video_mem_entry);
impl SingleLayoutBuffer {
pub fn raw_addr(&self) -> u64 {
self.0.addr.into()
}
pub fn raw_len(&self) -> u32 {
self.0.length.into()
}
}
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
unsafe impl ByteValued for SingleLayoutBuffer {}
/// Responses
#[repr(C)]
#[derive(Clone, Debug, Default)]
pub(crate) struct virtio_video_query_capability_resp {
pub hdr: virtio_video_cmd_hdr,
pub num_descs: Le32,
pub padding: Le32,
pub descs: Vec<virtio_video_format_desc>,
}
impl virtio_video_query_capability_resp {
pub fn append_descs(&mut self, descs: &Vec<virtio_video_format_desc>) {
for desc in descs {
self.descs.push(desc.clone());
}
self.num_descs = (self.descs.len() as u32).into();
}
}
impl ToBytes for virtio_video_query_capability_resp {
fn to_bytes(&self) -> Vec<u8> {
let mut ret = [
self.hdr.as_slice(),
self.num_descs.as_slice(),
self.padding.as_slice(),
]
.concat();
self.descs
.iter()
.for_each(|desc| ret.append(&mut desc.to_bytes()));
ret
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub struct virtio_video_query_control_resp_value {
pub num: Le32,
pub padding: Le32,
pub value: Le32,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub(crate) struct virtio_video_query_control_resp {
pub hdr: virtio_video_cmd_hdr,
pub resp: virtio_video_query_control_resp_value,
}
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
unsafe impl ByteValued for virtio_video_query_control_resp {}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub(crate) struct virtio_video_get_params_resp {
pub hdr: virtio_video_cmd_hdr,
pub params: virtio_video_params,
}
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
unsafe impl ByteValued for virtio_video_get_params_resp {}
bitflags! {
#[derive(Copy, Clone, Debug, Default)]
pub struct BufferFlags: u32 {
const ERR = 1 << 0;
const EOS = 1 << 1;
// Encoder only
const IFRAME = 1 << 2;
const PFRAME = 1 << 3;
const BFRAME = 1 << 4;
}
}
impl From<BufferFlags> for Le32 {
fn from(val: BufferFlags) -> Self {
Le32::from(val.bits())
}
}
impl From<Le32> for BufferFlags {
fn from(value: Le32) -> Self {
Self::from_bits_retain(<Le32 as Into<u32>>::into(value))
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub(crate) struct virtio_video_resource_queue_resp {
pub hdr: virtio_video_cmd_hdr,
pub timestamp: Le64,
pub flags: BufferFlags,
pub size: Le32,
}
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
unsafe impl ByteValued for virtio_video_resource_queue_resp {}
#[repr(C)]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum VideoControl {
#[default]
Default,
Bitrate(Le32),
BitratePeak(Le32),
BitrateMode(Le32),
ForceKeyframe(Le32),
Profile(Le32),
Level(Le32),
PrependSpsPpsToIdr(Le32),
}
impl VideoControl {
pub fn new_from_type(control_type: ControlType, value: Le32) -> Self {
match control_type {
ControlType::Bitrate => Self::Bitrate(value),
ControlType::BitrateMode => Self::BitrateMode(value),
ControlType::BitratePeak => Self::BitratePeak(value),
ControlType::Profile => Self::Profile(value),
ControlType::Level => Self::Level(value),
ControlType::PrependSpsPpsToIdr => Self::PrependSpsPpsToIdr(value),
ControlType::ForceKeyframe => Self::ForceKeyframe(value),
}
}
fn get_value(&self) -> Le32 {
match self {
Self::Default => 0.into(),
Self::Bitrate(value)
| Self::BitrateMode(value)
| Self::BitratePeak(value)
| Self::Profile(value)
| Self::Level(value)
| Self::PrependSpsPpsToIdr(value)
| Self::ForceKeyframe(value) => *value,
}
}
}
#[repr(C)]
#[derive(Clone, Debug, Default)]
pub(crate) struct virtio_video_get_control_resp {
pub hdr: virtio_video_cmd_hdr,
pub control: VideoControl,
}
pub(crate) enum VirtioVideoEventType {
Error = 0x0100,
DecoderResolutionChanged = 0x0200,
}
impl From<VirtioVideoEventType> for Le32 {
fn from(val: VirtioVideoEventType) -> Self {
Le32::from(val as u32)
}
}
#[derive(Debug)]
pub struct virtio_video_event {
pub event_type: Le32,
pub stream_id: Le32,
}
impl ToBytes for virtio_video_event {
fn to_bytes(&self) -> Vec<u8> {
[self.event_type.as_slice(), self.stream_id.as_slice()].concat()
}
}

View File

@ -0,0 +1,89 @@
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
mod null;
use std::path::Path;
use self::null::NullBackend;
use crate::{
stream::{ResourcePlane, Stream},
vhu_video::{BackendType, Result, VuVideoError},
video::{
virtio_video_event, virtio_video_params, BufferFlags, CmdResponseType, ControlType,
QueueType,
},
};
#[derive(Debug, Default)]
pub struct DqBufData {
pub index: u32,
pub flags: BufferFlags,
pub size: u32,
}
pub trait VideoBackend {
fn create_stream(
&mut self,
stream_id: u32,
in_memory_type: u32,
out_memory_type: u32,
coded_format: u32,
) -> CmdResponseType;
fn destroy_stream(&mut self, stream_id: u32) -> CmdResponseType;
fn query_capability(&self, queue_type: QueueType) -> CmdResponseType;
fn query_control(&self, control: ControlType) -> CmdResponseType;
fn clear_queue(&mut self, stream_id: u32, queue_type: QueueType) -> CmdResponseType;
fn get_params(&self, stream_id: u32, queue_type: QueueType) -> CmdResponseType;
fn set_params(&mut self, stream_id: u32, params: virtio_video_params) -> CmdResponseType;
fn create_resource(
&mut self,
stream_id: u32,
resource_id: u32,
planes_layout: u32,
planes: Vec<ResourcePlane>,
queue_type: QueueType,
) -> CmdResponseType;
fn destroy_resources(&mut self, stream_id: u32, queue_type: QueueType) -> CmdResponseType;
fn queue_resource(
&mut self,
stream_id: u32,
queue_type: QueueType,
resource_id: u32,
timestamp: u64,
bytes_used: Vec<u32>,
) -> CmdResponseType;
fn dequeue_resource(&self, stream_id: u32, queue_type: QueueType) -> Option<DqBufData>;
fn drain_stream(&mut self, stream_id: u32) -> CmdResponseType;
fn dequeue_event(&self, stream_id: u32) -> Option<virtio_video_event>;
fn stream(&self, _stream_id: &u32) -> Option<&Stream> {
None
}
fn stream_mut(&mut self, _stream_id: &u32) -> Option<&mut Stream> {
None
}
}
pub(crate) fn alloc_video_backend(
backend: BackendType,
video_path: &Path,
) -> Result<Box<dyn VideoBackend + Sync + Send>> {
match backend {
BackendType::Null => Ok(Box::new(
NullBackend::new(video_path).map_err(|_| VuVideoError::AccessVideoDeviceFile)?,
)),
}
}

View File

@ -0,0 +1,139 @@
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
use std::{io, path::Path};
use log::info;
use super::{DqBufData, VideoBackend};
use crate::{
stream::ResourcePlane,
video::{self, CmdError::*, CmdResponseType::Sync},
};
pub struct NullBackend;
impl VideoBackend for NullBackend {
fn create_stream(
&mut self,
stream_id: u32,
_in_memory_type: u32,
_out_memory_type: u32,
_coded_format: u32,
) -> video::CmdResponseType {
info!("null backend => Creating stream: {}", stream_id);
Sync(video::CmdResponse::OkNoData)
}
fn destroy_stream(&mut self, stream_id: u32) -> video::CmdResponseType {
info!("null backend => Destroying stream: {}", stream_id);
Sync(video::CmdResponse::OkNoData)
}
fn queue_resource(
&mut self,
stream_id: u32,
queue_type: video::QueueType,
resource_id: u32,
_timestamp: u64,
_bytes_used: Vec<u32>,
) -> video::CmdResponseType {
info!(
"null backend => Queue resource ({:?} queue, id {}), stream {}",
queue_type, resource_id, stream_id
);
Sync(video::CmdResponse::Error(InvalidOperation))
}
fn query_capability(&self, queue_type: video::QueueType) -> video::CmdResponseType {
info!("null backend => Queue capability, queue {:?}", queue_type);
Sync(video::CmdResponse::QueryCapability(Vec::new()))
}
fn query_control(&self, control: video::ControlType) -> video::CmdResponseType {
info!("null backend => Query control: {:?}", control);
Sync(video::CmdResponse::QueryControl(
video::VideoControl::Default,
))
}
fn clear_queue(
&mut self,
stream_id: u32,
queue_type: video::QueueType,
) -> video::CmdResponseType {
info!(
"null backend => Clear {:?} queue, stream {}",
queue_type, stream_id
);
Sync(video::CmdResponse::OkNoData)
}
fn get_params(&self, stream_id: u32, queue_type: video::QueueType) -> video::CmdResponseType {
info!(
"null backend => Get {:?} queue params, stream {}",
queue_type, stream_id
);
Sync(video::CmdResponse::Error(InvalidOperation))
}
fn set_params(
&mut self,
stream_id: u32,
_params: video::virtio_video_params,
) -> video::CmdResponseType {
info!("null backend => Set params, stream {}", stream_id);
Sync(video::CmdResponse::OkNoData)
}
fn create_resource(
&mut self,
stream_id: u32,
resource_id: u32,
_planes_layout: u32,
_planes: Vec<ResourcePlane>,
queue_type: video::QueueType,
) -> video::CmdResponseType {
info!(
"null backend => Create resource {}, {:?} queue, stream {}",
resource_id, queue_type, stream_id
);
Sync(video::CmdResponse::OkNoData)
}
fn destroy_resources(
&mut self,
stream_id: u32,
queue_type: video::QueueType,
) -> video::CmdResponseType {
info!(
"null backend => Clear resources, {:?} queue, stream {}",
queue_type, stream_id
);
Sync(video::CmdResponse::OkNoData)
}
fn drain_stream(&mut self, stream_id: u32) -> video::CmdResponseType {
info!("null backend => Drain stream {}", stream_id);
Sync(video::CmdResponse::OkNoData)
}
fn dequeue_event(&self, _stream_id: u32) -> Option<video::virtio_video_event> {
None
}
fn dequeue_resource(
&self,
_stream_id: u32,
_queue_type: video::QueueType,
) -> Option<DqBufData> {
None
}
}
impl NullBackend {
pub fn new(video_path: &Path) -> io::Result<Self> {
// Check if file exists
std::fs::File::create(video_path)?;
Ok(Self)
}
}