pub mod devices;
pub mod io;
pub mod ioctl;
pub mod memfd;
pub mod mmap;
pub mod poll;
pub mod protocol;
use io::ReadFromDescriptorChain;
use io::WriteToDescriptorChain;
use poll::SessionPoller;
pub use v4l2r;
use std::collections::HashMap;
use std::io::Result as IoResult;
use std::os::fd::BorrowedFd;
use anyhow::Context;
use log::error;
use protocol::*;
pub trait VirtioMediaEventQueue {
fn send_event(&mut self, event: V4l2Event);
fn send_error(&mut self, session_id: u32, errno: i32) {
self.send_event(V4l2Event::Error(ErrorEvent::new(session_id, errno)));
}
}
pub trait GuestMemoryRange {
fn as_ptr(&self) -> *const u8;
fn as_mut_ptr(&mut self) -> *mut u8;
}
pub trait VirtioMediaGuestMemoryMapper {
type GuestMemoryMapping: GuestMemoryRange;
fn new_mapping(&self, sgs: Vec<SgEntry>) -> anyhow::Result<Self::GuestMemoryMapping>;
}
pub trait VirtioMediaHostMemoryMapper {
fn add_mapping(
&mut self,
buffer: BorrowedFd,
length: u64,
offset: u64,
rw: bool,
) -> Result<u64, i32>;
fn remove_mapping(&mut self, shm_offset: u64) -> Result<(), i32>;
}
impl VirtioMediaHostMemoryMapper for () {
fn add_mapping(&mut self, _: BorrowedFd, _: u64, _: u64, _: bool) -> Result<u64, i32> {
Err(libc::ENOTTY)
}
fn remove_mapping(&mut self, _: u64) -> Result<(), i32> {
Err(libc::ENOTTY)
}
}
pub trait VirtioMediaDeviceSession {
fn poll_fd(&self) -> Option<BorrowedFd<'_>>;
}
pub trait VirtioMediaDevice<Reader: ReadFromDescriptorChain, Writer: WriteToDescriptorChain> {
type Session: VirtioMediaDeviceSession;
fn new_session(&mut self, session_id: u32) -> Result<Self::Session, i32>;
fn close_session(&mut self, session: Self::Session);
fn do_ioctl(
&mut self,
session: &mut Self::Session,
ioctl: V4l2Ioctl,
reader: &mut Reader,
writer: &mut Writer,
) -> IoResult<()>;
fn do_mmap(
&mut self,
session: &mut Self::Session,
flags: u32,
offset: u32,
) -> Result<(u64, u64), i32>;
fn do_munmap(&mut self, guest_addr: u64) -> Result<(), i32>;
fn process_events(&mut self, _session: &mut Self::Session) -> Result<(), i32> {
panic!("process_events needs to be implemented")
}
}
pub struct VirtioMediaDeviceRunner<Reader, Writer, Device, Poller>
where
Reader: ReadFromDescriptorChain,
Writer: WriteToDescriptorChain,
Device: VirtioMediaDevice<Reader, Writer>,
Poller: SessionPoller,
{
pub device: Device,
poller: Poller,
pub sessions: HashMap<u32, Device::Session>,
session_id_counter: u32,
}
impl<Reader, Writer, Device, Poller> VirtioMediaDeviceRunner<Reader, Writer, Device, Poller>
where
Reader: ReadFromDescriptorChain,
Writer: WriteToDescriptorChain,
Device: VirtioMediaDevice<Reader, Writer>,
Poller: SessionPoller,
{
pub fn new(device: Device, poller: Poller) -> Self {
Self {
device,
poller,
sessions: Default::default(),
session_id_counter: 0,
}
}
}
impl<Reader, Writer, Device, Poller> VirtioMediaDeviceRunner<Reader, Writer, Device, Poller>
where
Reader: ReadFromDescriptorChain,
Writer: WriteToDescriptorChain,
Device: VirtioMediaDevice<Reader, Writer>,
Poller: SessionPoller,
{
pub fn handle_command(&mut self, reader: &mut Reader, writer: &mut Writer) {
let hdr = match reader.read_obj::<CmdHeader>() {
Ok(hdr) => hdr,
Err(e) => {
error!("error while reading command header: {:#}", e);
let _ = writer.write_err_response(libc::EINVAL);
return;
}
};
let res = match hdr.cmd {
VIRTIO_MEDIA_CMD_OPEN => {
let session_id = self.session_id_counter;
match self.device.new_session(session_id) {
Ok(session) => {
if let Some(fd) = session.poll_fd() {
match self.poller.add_session(fd, session_id) {
Ok(()) => {
self.sessions.insert(session_id, session);
self.session_id_counter += 1;
writer.write_response(OpenResp::ok(session_id))
}
Err(e) => {
log::error!(
"failed to register poll FD for new session: {}",
e
);
self.device.close_session(session);
writer.write_err_response(e)
}
}
} else {
self.sessions.insert(session_id, session);
self.session_id_counter += 1;
writer.write_response(OpenResp::ok(session_id))
}
}
Err(e) => writer.write_err_response(e),
}
.context("while writing response for OPEN command")
}
.context("while writing response for OPEN command"),
VIRTIO_MEDIA_CMD_CLOSE => reader
.read_obj()
.context("while reading CLOSE command")
.map(|CloseCmd { session_id, .. }| {
if let Some(session) = self.sessions.remove(&session_id) {
if let Some(fd) = session.poll_fd() {
self.poller.remove_session(fd);
}
self.device.close_session(session);
}
}),
VIRTIO_MEDIA_CMD_IOCTL => reader
.read_obj()
.context("while reading IOCTL command")
.and_then(|IoctlCmd { session_id, code }| {
match self.sessions.get_mut(&session_id) {
Some(session) => match V4l2Ioctl::n(code) {
Some(ioctl) => self.device.do_ioctl(session, ioctl, reader, writer),
None => {
error!("unknown ioctl code {}", code);
writer.write_err_response(libc::ENOTTY)
}
},
None => writer.write_err_response(libc::EINVAL),
}
.context("while writing response for IOCTL command")
}),
VIRTIO_MEDIA_CMD_MMAP => reader
.read_obj()
.context("while reading MMAP command")
.and_then(
|MmapCmd {
session_id,
flags,
offset,
}| {
match self
.sessions
.get_mut(&session_id)
.ok_or(libc::EINVAL)
.and_then(|session| self.device.do_mmap(session, flags, offset))
{
Ok((guest_addr, size)) => {
writer.write_response(MmapResp::ok(guest_addr, size))
}
Err(e) => writer.write_err_response(e),
}
.context("while writing response for MMAP command")
},
),
VIRTIO_MEDIA_CMD_MUNMAP => reader
.read_obj()
.context("while reading UNMMAP command")
.and_then(
|MunmapCmd {
driver_addr: guest_addr,
}| {
match self.device.do_munmap(guest_addr) {
Ok(()) => writer.write_response(MunmapResp::ok()),
Err(e) => writer.write_err_response(e),
}
.context("while writing response for MUNMAP command")
},
),
_ => writer
.write_err_response(libc::ENOTTY)
.context("while writing error response for invalid command"),
};
if let Err(e) = res {
error!("error while processing command: {:#}", e);
let _ = writer.write_err_response(libc::EINVAL);
}
}
pub fn into_device(self) -> Device {
self.device
}
}