From 8b2a1235f0225e63f00c29f08e5f066b9b181419 Mon Sep 17 00:00:00 2001 From: Sergio Andres Rodriguez Orama Date: Mon, 11 Aug 2025 11:28:52 -0400 Subject: [PATCH] Port virtio media simple device as a vhost user media device. Run the device. ``` RUST_LOG=trace cargo run --bin simple_device -- --socket-path /tmp/simple_device.sock or RUST_LOG=trace bazel run simple_device:simple_device -- --socket-path /tmp/simple_device.sock ``` Run the VM. ``` cargo run -- --log-level=debug run --cpus 4 --mem 4096 \ --rwdisk /path/to/debian-12.img \ --params "root=/dev/vda1" \ --vhost-user media,socket=/tmp/simple_device.sock \ /path/to/bzImage v4l2-compliance -d0 -s ``` Bug: 445229097 --- base/cvd/MODULE.bazel | 11 + .../commands/vhost_user_media/BUILD.bazel | 0 .../host/commands/vhost_user_media/Cargo.toml | 34 + .../simple_device/BUILD.bazel | 13 + .../vhost_user_media/simple_device/Cargo.toml | 16 + .../simple_device/src/main.rs | 108 ++++ .../vhost_user_media/vhu_media/BUILD.bazel | 28 + .../vhost_user_media/vhu_media/Cargo.toml | 22 + .../vhost_user_media/vhu_media/src/lib.rs | 582 ++++++++++++++++++ 9 files changed, 814 insertions(+) create mode 100644 base/cvd/cuttlefish/host/commands/vhost_user_media/BUILD.bazel create mode 100644 base/cvd/cuttlefish/host/commands/vhost_user_media/Cargo.toml create mode 100644 base/cvd/cuttlefish/host/commands/vhost_user_media/simple_device/BUILD.bazel create mode 100644 base/cvd/cuttlefish/host/commands/vhost_user_media/simple_device/Cargo.toml create mode 100644 base/cvd/cuttlefish/host/commands/vhost_user_media/simple_device/src/main.rs create mode 100644 base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/BUILD.bazel create mode 100644 base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/Cargo.toml create mode 100644 base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/src/lib.rs diff --git a/base/cvd/MODULE.bazel b/base/cvd/MODULE.bazel index 917079894ca..bc13d352625 100644 --- a/base/cvd/MODULE.bazel +++ b/base/cvd/MODULE.bazel @@ -146,6 +146,17 @@ vhost_user_input_crates.from_cargo( ) use_repo(vhost_user_input_crates, "vhost_user_input_crates") +vhost_user_media_workspace_crates = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") +vhost_user_media_workspace_crates.from_cargo( + name = "vhost_user_media_workspace_crates", + cargo_config = "@//build_external/crosvm:crosvm.config.toml", + manifests = [ + "//cuttlefish/host/commands/vhost_user_media:Cargo.toml", + ], + host_tools = "@rust_host_tools_nightly", +) +use_repo(vhost_user_media_workspace_crates, "vhost_user_media_workspace_crates") + git_repository = use_repo_rule("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") git_repository( diff --git a/base/cvd/cuttlefish/host/commands/vhost_user_media/BUILD.bazel b/base/cvd/cuttlefish/host/commands/vhost_user_media/BUILD.bazel new file mode 100644 index 00000000000..e69de29bb2d diff --git a/base/cvd/cuttlefish/host/commands/vhost_user_media/Cargo.toml b/base/cvd/cuttlefish/host/commands/vhost_user_media/Cargo.toml new file mode 100644 index 00000000000..ac1c34dc717 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/vhost_user_media/Cargo.toml @@ -0,0 +1,34 @@ +[workspace] +members = [ + "simple_device", + "vhu_media", +] + +[workspace.dependencies] +rng = { path = "rng" } +vhu_media = { path = "vhu_media" } +anyhow = { version = "1.0.97", features = [ "default", "std" ] } +clap = { version = "4.5", features = ["derive"] } +env_logger = "0.11" +libc = "0.2" +log = "0.4" +thiserror = "2.0" +vhost = { version = "0.15", features = ["vhost-user-backend"] } +vhost-user-backend = "0.21" +virtio-bindings = "0.2.6" +virtio-media = "0.0.7" +virtio-queue = "0.16.0" +vm-allocator = "0.1.3" +vm-memory = "0.16.2" +vmm-sys-util = "0.15.0" +v4l2r = { version = "0.0.7", features = ["arch64"] } +uuid = { version = "1.8.0", features=["v4"] } +zerocopy = { version = "0.8.13", features = ["derive"] } +bitflags = "2.3" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1" + +[patch.crates-io] +vhost = { path = "/usr/local/google/home/sorama/code/github.com/forks/vhost/vhost" } +vhost-user-backend = { path = "/usr/local/google/home/sorama/code/github.com/forks/vhost/vhost-user-backend" } + diff --git a/base/cvd/cuttlefish/host/commands/vhost_user_media/simple_device/BUILD.bazel b/base/cvd/cuttlefish/host/commands/vhost_user_media/simple_device/BUILD.bazel new file mode 100644 index 00000000000..36e0307ccb9 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/vhost_user_media/simple_device/BUILD.bazel @@ -0,0 +1,13 @@ +load("@rules_rust//rust:defs.bzl", "rust_binary") +load("@vhost_user_media_workspace_crates//:defs.bzl", "all_crate_deps") + +package(default_visibility = ["//:android_cuttlefish"]) + +rust_binary( + name = "simple_device", + srcs = ["src/main.rs"], + edition = "2024", + deps = [ + "//cuttlefish/host/commands/vhost_user_media/vhu_media", + ] + all_crate_deps(), +) diff --git a/base/cvd/cuttlefish/host/commands/vhost_user_media/simple_device/Cargo.toml b/base/cvd/cuttlefish/host/commands/vhost_user_media/simple_device/Cargo.toml new file mode 100644 index 00000000000..64f008efd28 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/vhost_user_media/simple_device/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "simple_device" +version = "0.1.0" +edition = "2024" + +[dependencies] +clap = { workspace = true } +env_logger = { workspace = true } +libc = { workspace = true } +log = { workspace = true } +thiserror = { workspace = true } +v4l2r = { workspace = true } +vhost-user-backend = { workspace = true } +virtio-media = { workspace = true } +vm-memory = { workspace = true } +vhu_media = { workspace = true } diff --git a/base/cvd/cuttlefish/host/commands/vhost_user_media/simple_device/src/main.rs b/base/cvd/cuttlefish/host/commands/vhost_user_media/simple_device/src/main.rs new file mode 100644 index 00000000000..9ce3ed8b132 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/vhost_user_media/simple_device/src/main.rs @@ -0,0 +1,108 @@ +// Copyright 2025, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! simple_device + +use std::{ + path::PathBuf, + process::exit, + sync::{Arc, RwLock}, + thread::{JoinHandle, spawn}, +}; + +use clap::Parser; +use log::error; +use thiserror::Error; +use vhost_user_backend::VhostUserDaemon; +use vhu_media::VhuMediaBackend; +use virtio_media::devices::simple_device; +use virtio_media::protocol::VirtioMediaDeviceConfig; +use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; + +#[derive(Debug, Error)] +pub(crate) enum Error { + #[error("Could not create daemon: {0}")] + CouldNotCreateDaemon(vhost_user_backend::Error), + #[error("Fatal error: {0}")] + ServeFailed(vhost_user_backend::Error), +} + +type Result = std::result::Result; + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct CmdLineArgs { + /// Location of vhost-user Unix domain socket. + #[clap(short, long, value_name = "SOCKET")] + socket_path: PathBuf, +} + +#[derive(PartialEq, Debug)] +struct Config { + socket_path: PathBuf, +} + +impl TryFrom for Config { + type Error = Error; + + fn try_from(args: CmdLineArgs) -> Result { + Ok(Config { + socket_path: args.socket_path, + }) + } +} + +fn start_backend(args: CmdLineArgs) -> Result<()> { + let config = Config::try_from(args)?; + let socket_path = config.socket_path.clone(); + let handle: JoinHandle> = spawn(move || { + loop { + let mut card = [0u8; 32]; + let card_name = "simple_device"; + card[0..card_name.len()].copy_from_slice(card_name.as_bytes()); + use virtio_media::v4l2r::ioctl::Capabilities; + let config = VirtioMediaDeviceConfig { + // device_caps: (Capabilities::VIDEO_CAPTURE_MPLANE | Capabilities::STREAMING).bits(), + device_caps: (Capabilities::VIDEO_CAPTURE | Capabilities::STREAMING).bits(), + // VFL_TYPE_VIDEO + device_type: 0, + card, + }; + let backend = Arc::new(RwLock::new(VhuMediaBackend::new( + config, + |event_queue, host_mapper| { + simple_device::SimpleCaptureDevice::new(event_queue, host_mapper) + }, + ))); + let mut daemon = VhostUserDaemon::new( + String::from("vhost-user-media-backend"), + backend, + GuestMemoryAtomic::new(GuestMemoryMmap::new()), + ) + .map_err(Error::CouldNotCreateDaemon)?; + daemon.serve(&socket_path).map_err(Error::ServeFailed)?; + } + }); + + handle.join().map_err(std::panic::resume_unwind).unwrap() +} + +fn main() { + env_logger::init(); + + if let Err(e) = start_backend(CmdLineArgs::parse()) { + error!("{e}"); + exit(1); + } +} diff --git a/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/BUILD.bazel b/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/BUILD.bazel new file mode 100644 index 00000000000..61a89dd30bf --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/BUILD.bazel @@ -0,0 +1,28 @@ +load("@rules_rust//rust:defs.bzl", "rust_library") +load("@vhost_user_media_workspace_crates//:defs.bzl", "crate_deps") + +package(default_visibility = ["//visibility:public"]) + +rust_library( + name = "vhu_media", + srcs = ["src/lib.rs"], + edition = "2024", + deps = crate_deps([ + "anyhow", + "env_logger", + "libc", + "log", + "thiserror", + "vhost", + "vhost-user-backend", + "virtio-bindings", + "virtio-media", + "virtio-queue", + "vm-allocator", + "vm-memory", + "vmm-sys-util", + "uuid", + "zerocopy", + "bitflags", + ]), +) diff --git a/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/Cargo.toml b/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/Cargo.toml new file mode 100644 index 00000000000..9fa53c47414 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "vhu_media" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = { workspace = true } +env_logger = { workspace = true } +libc = { workspace = true } +log = { workspace = true } +thiserror = { workspace = true } +vhost = { workspace = true } +vhost-user-backend = { workspace = true } +virtio-bindings = { workspace = true } +virtio-media = { workspace = true } +virtio-queue = { workspace = true } +vm-allocator = { workspace = true } +vm-memory = { workspace = true } +vmm-sys-util = { workspace = true } +uuid = { workspace = true } +zerocopy = { workspace = true } +bitflags = { workspace = true } diff --git a/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/src/lib.rs b/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/src/lib.rs new file mode 100644 index 00000000000..579857f7823 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/src/lib.rs @@ -0,0 +1,582 @@ +// Copyright 2025, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::HashMap; +use std::convert; +use std::io; +use std::os::fd::BorrowedFd; +use std::os::unix::io::AsRawFd; + +use libc::_SC_PAGESIZE; +use libc::sysconf; +use log::error; +use log::info; +use thiserror::Error as ThisError; +use vhost::vhost_user::Backend; +use vhost::vhost_user::VhostUserFrontendReqHandler; +use vhost::vhost_user::message::VhostUserMMap; +use vhost::vhost_user::message::VhostUserMMapFlags; +use vhost::vhost_user::message::VhostUserProtocolFeatures; +use vhost::vhost_user::message::VhostUserShMemConfig; +use vhost::vhost_user::message::VhostUserVirtioFeatures; +use vhost_user_backend::{VhostUserBackendMut, VringRwLock, VringT}; +use virtio_bindings::bindings::virtio_config::VIRTIO_F_NOTIFY_ON_EMPTY; +use virtio_bindings::bindings::virtio_config::VIRTIO_F_VERSION_1; +use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX; +use virtio_media::VirtioMediaDevice; +use virtio_media::VirtioMediaDeviceSession; +use virtio_media::VirtioMediaEventQueue; +use virtio_media::VirtioMediaHostMemoryMapper; +use virtio_media::io::ReadFromDescriptorChain; +use virtio_media::io::WriteToDescriptorChain; +use virtio_media::protocol::CloseCmd; +use virtio_media::protocol::CmdHeader; +use virtio_media::protocol::IoctlCmd; +use virtio_media::protocol::MmapCmd; +use virtio_media::protocol::MmapResp; +use virtio_media::protocol::MunmapCmd; +use virtio_media::protocol::MunmapResp; +use virtio_media::protocol::OpenResp; +use virtio_media::protocol::V4l2Event; +use virtio_media::protocol::VirtioMediaDeviceConfig; +use virtio_media::protocol::{self, V4l2Ioctl}; +use virtio_queue::DescriptorChain; +use virtio_queue::QueueOwnedT; +use virtio_queue::Reader; +use virtio_queue::Writer; +use vm_allocator::AddressAllocator; +use vm_allocator::RangeInclusive; +use vm_memory::GuestAddressSpace; +use vm_memory::GuestMemoryAtomic; +use vm_memory::GuestMemoryLoadGuard; +use vm_memory::GuestMemoryMmap; +use vmm_sys_util::epoll::EventSet; +use vmm_sys_util::event::{EventConsumer, EventNotifier}; +use vmm_sys_util::event::{EventFlag, new_event_consumer_and_notifier}; + +#[derive(Debug, ThisError)] +/// Errors related to vhost-user-media daemon. +pub(crate) enum VhuMediaBackendError { + #[error("Failed to handle event, didn't match EPOLLIN")] + HandleEventNotEpollIn, + #[error("Failed to handle unknown event")] + HandleEventUnknown, + #[error("Descriptor chain error")] + DescriptorChainError(virtio_queue::Error), + #[error("I/O error")] + Io(#[from] std::io::Error), + #[error("Virtio media device not created")] + VirtioMediaDeviceNotCreated, + #[error("Descriptor is unavailable")] + DescriptorUnavailable, +} + +impl convert::From for io::Error { + fn from(e: VhuMediaBackendError) -> Self { + io::Error::other(e) + } +} + +type VhuMediaDescriptorChain = DescriptorChain>>; + +pub struct HostMemoryMapper { + backend: Backend, + allocator: AddressAllocator, + allocated_ranges_map: HashMap, +} + +impl HostMemoryMapper { + fn new(backend: Backend, allocator: AddressAllocator) -> HostMemoryMapper { + HostMemoryMapper { + backend: backend, + allocator: allocator, + allocated_ranges_map: HashMap::new(), + } + } +} + +impl VirtioMediaHostMemoryMapper for HostMemoryMapper { + fn add_mapping( + &mut self, + buffer: BorrowedFd, + length: u64, + _offset: u64, + rw: bool, + ) -> Result { + let range = self + .allocator + .allocate( + length, + pagesize() as u64, + vm_allocator::AllocPolicy::FirstMatch, + ) + .map_err(|e| { + error!("allocate error: {}", e); + libc::ENOMEM + })?; + self.allocated_ranges_map.insert(range.start(), range.end()); + let mut flags = VhostUserMMapFlags::empty(); + if rw { + flags |= VhostUserMMapFlags::WRITABLE; + } + let msg = VhostUserMMap { + shmid: 0, + padding: [0, 0, 0, 0, 0, 0, 0], + fd_offset: 0, + shm_offset: range.start(), + len: length, + flags: flags.bits(), + }; + + match self.backend.shmem_map(&msg, &buffer.as_raw_fd()) { + Ok(_) => Ok(range.start()), + Err(e) => { + error!("memory map file request error: {}", e); + Err(libc::EINVAL) + } + } + } + + fn remove_mapping(&mut self, shm_offset: u64) -> Result<(), i32> { + let end = match self.allocated_ranges_map.remove(&shm_offset) { + Some(v) => v, + None => { + error!("alloc not found, start: {}", shm_offset); + return Err(libc::EINVAL); + } + }; + let range = RangeInclusive::new(shm_offset, end).map_err(|e| { + error!("invalid range: {}", e); + libc::EINVAL + })?; + self.allocator.free(&range).map_err(|e| { + error!("free error: {}", e); + libc::EINVAL + })?; + let msg = VhostUserMMap { + shmid: 0, + padding: [0, 0, 0, 0, 0, 0, 0], + fd_offset: 0, + shm_offset: shm_offset, + len: 1, + flags: 0, + }; + + match self.backend.shmem_unmap(&msg) { + Ok(_) => Ok(()), + Err(e) => { + error!("memory unmap file request error: {}", e); + Err(libc::EINVAL) + } + } + } +} + +pub struct EventQueue { + mem: GuestMemoryLoadGuard, + vring: VringRwLock, +} + +impl EventQueue { + fn send_events(&mut self, event: V4l2Event) -> Result<(), VhuMediaBackendError> { + let vring = self.vring.clone(); + let mem = self.mem.clone(); + let requests: Vec<_> = vring + .get_mut() + .get_queue_mut() + .iter(mem) + .map_err(|e| VhuMediaBackendError::DescriptorChainError(e))? + .collect(); + if requests.is_empty() { + return Err(VhuMediaBackendError::DescriptorUnavailable); + } + for desc_chain in requests.clone() { + let mem = self.mem.clone(); + let head_index = desc_chain.head_index(); + let mut writer = desc_chain + .writer(&mem) + .map_err(|e| VhuMediaBackendError::DescriptorChainError(e))?; + if let Err(e) = match event { + V4l2Event::DequeueBuffer(e) => WriteToDescriptorChain::write_obj(&mut writer, e), + V4l2Event::Error(e) => WriteToDescriptorChain::write_obj(&mut writer, e), + V4l2Event::Event(e) => WriteToDescriptorChain::write_obj(&mut writer, e), + } { + return Err(VhuMediaBackendError::Io(e)); + } + vring + .get_mut() + .add_used(head_index, writer.bytes_written() as u32) + .map_err(|e| VhuMediaBackendError::DescriptorChainError(e))?; + vring + .signal_used_queue() + .map_err(|e| VhuMediaBackendError::Io(e))?; + break; + } + Ok(()) + } +} + +impl VirtioMediaEventQueue for EventQueue { + fn send_event(&mut self, event: V4l2Event) { + if let Err(e) = self.send_events(event) { + error!("send event failed with error: {}", e); + } + } +} + +pub struct VhuMediaBackend< + S: VirtioMediaDeviceSession, + D: for<'a> VirtioMediaDevice, Writer<'a>>, + F: Fn(EventQueue, HostMemoryMapper) -> D, +> { + backend: Option, + config: VirtioMediaDeviceConfig, + event_idx: bool, + exit_event_fds: Vec<(EventConsumer, EventNotifier)>, + mem: Option>, + sessions: HashMap, + session_id_counter: u32, + device: Option, + create_device_fn: F, +} + +impl VhuMediaBackend +where + S: VirtioMediaDeviceSession + Send + Sync, + D: for<'a> VirtioMediaDevice, Writer<'a>, Session = S> + Send + Sync, + F: Fn(EventQueue, HostMemoryMapper) -> D + Send + Sync, +{ + pub fn new(config: VirtioMediaDeviceConfig, create_device_fn: F) -> Self { + let mut backend = VhuMediaBackend { + backend: None, + config, + event_idx: false, + exit_event_fds: vec![], + mem: None, + sessions: Default::default(), + session_id_counter: 0, + device: None, + create_device_fn: create_device_fn, + }; + // Create a event_fd for each thread. We make it NONBLOCKing in + // order to allow tests maximum flexibility in checking whether + // signals arrived or not. + backend.exit_event_fds = (0..backend.queues_per_thread().len()) + .map(|_| { + new_event_consumer_and_notifier(EventFlag::NONBLOCK) + .expect("Failed to new EventNotifier and EventConsumer") + }) + .collect(); + + backend + } + + fn process_commandq_requests( + &mut self, + requests: Vec, + vring: &VringRwLock, + ) -> Result<(), VhuMediaBackendError> { + if requests.is_empty() { + info!("no pending requests"); + return Ok(()); + } + let device = self + .device + .as_mut() + .ok_or(VhuMediaBackendError::VirtioMediaDeviceNotCreated)?; + let mem = self.mem.as_ref().unwrap().clone(); + for desc_chain in requests.clone() { + let head_index = desc_chain.head_index(); + let mut reader = desc_chain + .clone() + .reader(&mem) + .map_err(|e| VhuMediaBackendError::DescriptorChainError(e))?; + let mut writer = desc_chain + .writer(&mem) + .map_err(|e| VhuMediaBackendError::DescriptorChainError(e))?; + let hdr = ReadFromDescriptorChain::read_obj::(&mut reader) + .map_err(|e| VhuMediaBackendError::Io(e))?; + match hdr.cmd { + protocol::VIRTIO_MEDIA_CMD_OPEN => { + let session_id = self.session_id_counter; + match device.new_session(session_id) { + Ok(session) => { + self.sessions.insert(session_id, session); + self.session_id_counter += 1; + writer + .write_response(OpenResp::ok(session_id)) + .map_err(|e| VhuMediaBackendError::Io(e))?; + } + Err(e) => { + error!("device new session error: {}", e); + writer + .write_err_response(e) + .map_err(|e| VhuMediaBackendError::Io(e))?; + } + } + } + protocol::VIRTIO_MEDIA_CMD_CLOSE => { + let cmd = ReadFromDescriptorChain::read_obj::(&mut reader) + .map_err(|e| VhuMediaBackendError::Io(e))?; + match self.sessions.remove(&cmd.session_id) { + Some(session) => device.close_session(session), + None => { + error!("session id not found: {}", cmd.session_id); + writer + .write_err_response(libc::EINVAL) + .map_err(|e| VhuMediaBackendError::Io(e))?; + } + } + } + protocol::VIRTIO_MEDIA_CMD_IOCTL => { + let cmd = ReadFromDescriptorChain::read_obj::(&mut reader) + .map_err(|e| VhuMediaBackendError::Io(e))?; + match self.sessions.get_mut(&cmd.session_id) { + Some(session) => match V4l2Ioctl::n(cmd.code) { + Some(ioctl) => { + device + .do_ioctl(session, ioctl, &mut reader, &mut writer) + .map_err(|e| VhuMediaBackendError::Io(e))?; + } + None => { + error!("unknown ioctl code {}", cmd.code); + writer + .write_err_response(libc::ENOTTY) + .map_err(|e| VhuMediaBackendError::Io(e))?; + } + }, + None => { + error!("session id not found: {}", cmd.session_id); + writer + .write_err_response(libc::EINVAL) + .map_err(|e| VhuMediaBackendError::Io(e))?; + } + } + } + protocol::VIRTIO_MEDIA_CMD_MMAP => { + let cmd = ReadFromDescriptorChain::read_obj::(&mut reader) + .map_err(|e| VhuMediaBackendError::Io(e))?; + match self.sessions.get_mut(&cmd.session_id) { + Some(session) => match device.do_mmap(session, cmd.flags, cmd.offset) { + Ok((guest_addr, size)) => { + writer + .write_response(MmapResp::ok(guest_addr, size)) + .map_err(|e| VhuMediaBackendError::Io(e))?; + } + Err(e) => { + error!("device mmap error: {}", e); + writer + .write_err_response(e) + .map_err(|e| VhuMediaBackendError::Io(e))?; + } + }, + None => { + error!("session id not found: {}", cmd.session_id); + writer + .write_err_response(libc::EINVAL) + .map_err(|e| VhuMediaBackendError::Io(e))?; + } + } + } + protocol::VIRTIO_MEDIA_CMD_MUNMAP => { + let cmd = ReadFromDescriptorChain::read_obj::(&mut reader) + .map_err(|e| VhuMediaBackendError::Io(e))?; + match device.do_munmap(cmd.driver_addr) { + Ok(()) => { + writer + .write_response(MunmapResp::ok()) + .map_err(|e| VhuMediaBackendError::Io(e))?; + } + Err(e) => { + error!("device munmap error: {}", e); + writer + .write_err_response(libc::EINVAL) + .map_err(|e| VhuMediaBackendError::Io(e))?; + } + } + } + unknown_cmd => { + error!("unknown virtio media command: {}", unknown_cmd); + writer + .write_err_response(libc::ENOTTY) + .map_err(|e| VhuMediaBackendError::Io(e))?; + } + } + vring + .get_mut() + .add_used(head_index, writer.bytes_written() as u32) + .map_err(|e| VhuMediaBackendError::DescriptorChainError(e))?; + } + + Ok(()) + } + + fn process_commandq_queue(&mut self, vring: &VringRwLock) -> Result<(), VhuMediaBackendError> { + let requests: Vec<_> = vring + .get_mut() + .get_queue_mut() + .iter(self.mem.as_ref().unwrap().clone()) + .map_err(|e| VhuMediaBackendError::DescriptorChainError(e))? + .collect(); + return match self.process_commandq_requests(requests, vring) { + Ok(()) => { + vring + .signal_used_queue() + .map_err(|e| VhuMediaBackendError::Io(e))?; + Ok(()) + } + Err(e) => Err(e), + }; + } +} + +const NUM_QUEUES: usize = 2; +// Use 32768 to avoid Err(InvalidParam) in vhost-user-backend/src/handler.rs:set_vring_num +const QUEUE_SIZE: usize = 32768; + +const COMMANDQ: u16 = 0; + +const EVENTQ: u16 = 1; + +impl VhostUserBackendMut for VhuMediaBackend +where + S: VirtioMediaDeviceSession + Send + Sync, + D: for<'a> VirtioMediaDevice, Writer<'a>, Session = S> + Sync + Send, + F: Fn(EventQueue, HostMemoryMapper) -> D + Sync + Send, +{ + type Vring = VringRwLock; + type Bitmap = (); + + fn num_queues(&self) -> usize { + NUM_QUEUES + } + + fn max_queue_size(&self) -> usize { + QUEUE_SIZE + } + + fn features(&self) -> u64 { + (1 << VIRTIO_F_VERSION_1) + | (1 << VIRTIO_F_NOTIFY_ON_EMPTY) + | (1 << VIRTIO_RING_F_EVENT_IDX) + | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() + } + + fn protocol_features(&self) -> vhost::vhost_user::message::VhostUserProtocolFeatures { + VhostUserProtocolFeatures::MQ + | VhostUserProtocolFeatures::CONFIG + | VhostUserProtocolFeatures::BACKEND_REQ + | VhostUserProtocolFeatures::SHMEM + } + + fn set_event_idx(&mut self, enabled: bool) { + self.event_idx = enabled; + } + + fn update_memory(&mut self, mem: GuestMemoryAtomic) -> io::Result<()> { + self.mem = Some(mem.memory()); + Ok(()) + } + + fn set_backend_req_fd(&mut self, backend: Backend) { + self.backend = Some(backend); + } + + fn handle_event( + &mut self, + device_event: u16, + evset: EventSet, + vrings: &[VringRwLock], + _thread_id: usize, + ) -> io::Result<()> { + if evset != EventSet::IN { + return Err(VhuMediaBackendError::HandleEventNotEpollIn.into()); + } + match device_event { + COMMANDQ => { + let vring = &vrings[COMMANDQ as usize]; + if self.event_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 { + vring.disable_notification().unwrap(); + self.process_commandq_queue(vring)?; + if !vring.enable_notification().unwrap() { + break; + } + } + } else { + self.process_commandq_queue(vring)?; + } + } + EVENTQ => { + let vring = &vrings[EVENTQ as usize]; + let event_queue = EventQueue { + vring: vring.clone(), + mem: self.mem.as_ref().unwrap().clone(), + }; + const HOST_MAPPER_RANGE: u64 = 1 << 32; + let allocator = + vm_allocator::AddressAllocator::new(0, HOST_MAPPER_RANGE - 1).unwrap(); + let host_mapper = + HostMemoryMapper::new(self.backend.as_ref().unwrap().clone(), allocator); + self.device = Some((self.create_device_fn)(event_queue, host_mapper)); + } + _ => { + return Err(VhuMediaBackendError::HandleEventUnknown.into()); + } + } + Ok(()) + } + + fn exit_event(&self, thread_index: usize) -> Option<(EventConsumer, EventNotifier)> { + self.exit_event_fds.get(thread_index).map(|(s, r)| { + ( + s.try_clone().expect("Failed to clone EventConsumer"), + r.try_clone().expect("Failed to clone EventNotifier"), + ) + }) + } + + fn queues_per_thread(&self) -> Vec { + return vec![3]; + } + + fn get_config(&self, offset: u32, size: u32) -> Vec { + let offset = offset as usize; + let size = size as usize; + let buf = self.config.as_ref(); + if offset + size > buf.len() { + return Vec::new(); + } + + buf[offset..offset + size].to_vec() + } + + fn get_shmem_config(&self) -> io::Result { + // We need a 32-bit address space as m2m devices start their CAPTURE buffers' offsets + // at 2GB. + Ok(VhostUserShMemConfig::new(1, &[1 << 32])) + } +} + +/// Safe wrapper for `sysconf(_SC_PAGESIZE)`. +#[inline(always)] +fn pagesize() -> usize { + // SAFETY: + // Trivially safe + unsafe { sysconf(_SC_PAGESIZE) as usize } +}