mirror of
https://github.com/rust-vmm/vhost-device.git
synced 2025-12-26 14:41:23 +00:00
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:
parent
1ccb3b6e58
commit
5edf1dfb7b
@ -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
86
staging/Cargo.lock
generated
@ -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"
|
||||
|
||||
@ -2,4 +2,5 @@
|
||||
resolver = "2"
|
||||
members = [
|
||||
"vhost-device-sound",
|
||||
"vhost-device-video",
|
||||
]
|
||||
|
||||
15
staging/vhost-device-video/CHANGELOG.md
Normal file
15
staging/vhost-device-video/CHANGELOG.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Changelog
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
### Changed
|
||||
|
||||
### Fixed
|
||||
|
||||
### Deprecated
|
||||
|
||||
## [0.1.0]
|
||||
|
||||
First release
|
||||
|
||||
37
staging/vhost-device-video/Cargo.toml
Normal file
37
staging/vhost-device-video/Cargo.toml
Normal 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"] }
|
||||
1
staging/vhost-device-video/LICENSE-APACHE
Symbolic link
1
staging/vhost-device-video/LICENSE-APACHE
Symbolic link
@ -0,0 +1 @@
|
||||
../../LICENSE-APACHE
|
||||
1
staging/vhost-device-video/LICENSE-BSD-3-Clause
Symbolic link
1
staging/vhost-device-video/LICENSE-BSD-3-Clause
Symbolic link
@ -0,0 +1 @@
|
||||
../../LICENSE-BSD-3-Clause
|
||||
14
staging/vhost-device-video/README.md
Normal file
14
staging/vhost-device-video/README.md
Normal 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)
|
||||
7
staging/vhost-device-video/rustfmt.toml
Normal file
7
staging/vhost-device-video/rustfmt.toml
Normal 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
|
||||
128
staging/vhost-device-video/src/main.rs
Normal file
128
staging/vhost-device-video/src/main.rs
Normal 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())
|
||||
}
|
||||
373
staging/vhost-device-video/src/stream.rs
Normal file
373
staging/vhost-device-video/src/stream.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
260
staging/vhost-device-video/src/vhu_video.rs
Normal file
260
staging/vhost-device-video/src/vhu_video.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
573
staging/vhost-device-video/src/vhu_video_thread.rs
Normal file
573
staging/vhost-device-video/src/vhu_video_thread.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
943
staging/vhost-device-video/src/video.rs
Normal file
943
staging/vhost-device-video/src/video.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
89
staging/vhost-device-video/src/video_backends.rs
Normal file
89
staging/vhost-device-video/src/video_backends.rs
Normal 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)?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
139
staging/vhost-device-video/src/video_backends/null.rs
Normal file
139
staging/vhost-device-video/src/video_backends/null.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user