summary refs log blame commit diff
path: root/devices/src/virtio/video/mod.rs
blob: 1b3058a5789ebf2acc40f31eac12c802187a7512 (plain) (tree)












































                                                                                       

                                           


























                                                                
                                                                                              






























































































































































                                                                                                    


                                                                                  

















                                                                   
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//! This module implements the virtio video encoder and decoder devices.
//! The current implementation uses [v3 RFC] of the virtio-video protocol.
//!
//! [v3 RFC]: https://markmail.org/thread/wxdne5re7aaugbjg

use std::fmt::{self, Display};
use std::os::unix::io::{AsRawFd, RawFd};
use std::thread;

use data_model::{DataInit, Le32};
use sys_util::{error, Error as SysError, EventFd, GuestMemory};

use crate::virtio::resource_bridge::ResourceRequestSocket;
use crate::virtio::virtio_device::VirtioDevice;
use crate::virtio::{self, copy_config, DescriptorError, Interrupt, VIRTIO_F_VERSION_1};

#[macro_use]
mod macros;
mod command;
mod control;
mod decoder;
mod device;
mod encoder;
mod error;
mod event;
mod format;
mod params;
mod protocol;
mod response;
mod worker;

use command::ReadCmdError;
use device::AsyncCmdTag;
use worker::Worker;

const QUEUE_SIZE: u16 = 256;
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE, QUEUE_SIZE];

/// An error indicating something went wrong in virtio-video's worker.
#[derive(Debug)]
pub enum Error {
    /// Failed to create a libvda instance.
    LibvdaCreationFailed(libvda::Error),
    /// Creating PollContext failed.
    PollContextCreationFailed(SysError),
    /// A DescriptorChain contains invalid data.
    InvalidDescriptorChain(DescriptorError),
    /// Invalid output buffer is specified for EOS notification.
    InvalidEOSResource { stream_id: u32, resource_id: u32 },
    /// No available descriptor in which an event is written to.
    DescriptorNotAvailable,
    /// Output buffer for EOS is unavailable.
    NoEOSBuffer { stream_id: u32 },
    /// Error while polling for events.
    PollError(SysError),
    /// Failed to read a virtio-video command.
    ReadFailure(ReadCmdError),
    /// Got response for an unexpected asynchronous command.
    UnexpectedResponse(AsyncCmdTag),
    /// Failed to write an event into the event queue.
    WriteEventFailure {
        event: event::VideoEvt,
        error: std::io::Error,
    },
}

impl Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use Error::*;
        match self {
            LibvdaCreationFailed(e) => write!(f, "failed to create a libvda instance: {}", e),
            PollContextCreationFailed(e) => write!(f, "failed to create PollContext: {}", e),
            InvalidDescriptorChain(e) => write!(f, "DescriptorChain contains invalid data: {}", e),
            InvalidEOSResource {
                stream_id,
                resource_id,
            } => write!(
                f,
                "invalid resource {} was specified for stream {}'s EOS",
                resource_id, stream_id
            ),
            DescriptorNotAvailable => {
                write!(f, "no available descriptor in which an event is written to")
            }
            NoEOSBuffer { stream_id } => write!(
                f,
                "no output resource is available to notify EOS: {}",
                stream_id
            ),
            PollError(err) => write!(f, "failed to poll events: {}", err),
            ReadFailure(e) => write!(f, "failed to read a command from the guest: {}", e),
            UnexpectedResponse(tag) => {
                write!(f, "got a response for an untracked command: {:?}", tag)
            }
            WriteEventFailure { event, error } => write!(
                f,
                "failed to write an event {:?} into event queue: {}",
                event, error
            ),
        }
    }
}

impl std::error::Error for Error {}
pub type Result<T> = std::result::Result<T, Error>;

#[derive(Debug)]
pub enum VideoDeviceType {
    Decoder,
    Encoder,
}

pub struct VideoDevice {
    device_type: VideoDeviceType,
    kill_evt: Option<EventFd>,
    resource_bridge: Option<ResourceRequestSocket>,
}

impl VideoDevice {
    pub fn new(
        device_type: VideoDeviceType,
        resource_bridge: Option<ResourceRequestSocket>,
    ) -> VideoDevice {
        VideoDevice {
            device_type,
            kill_evt: None,
            resource_bridge,
        }
    }
}

impl Drop for VideoDevice {
    fn drop(&mut self) {
        if let Some(kill_evt) = self.kill_evt.take() {
            // Ignore the result because there is nothing we can do about it.
            let _ = kill_evt.write(1);
        }
    }
}

impl VirtioDevice for VideoDevice {
    fn keep_fds(&self) -> Vec<RawFd> {
        let mut keep_fds = Vec::new();
        if let Some(resource_bridge) = &self.resource_bridge {
            keep_fds.push(resource_bridge.as_raw_fd());
        }
        keep_fds
    }

    fn device_type(&self) -> u32 {
        match &self.device_type {
            VideoDeviceType::Decoder => virtio::TYPE_VIDEO_DEC,
            VideoDeviceType::Encoder => virtio::TYPE_VIDEO_ENC,
        }
    }

    fn queue_max_sizes(&self) -> &[u16] {
        QUEUE_SIZES
    }

    fn features(&self) -> u64 {
        1u64 << VIRTIO_F_VERSION_1
            | 1u64 << protocol::VIRTIO_VIDEO_F_RESOURCE_NON_CONTIG
            | 1u64 << protocol::VIRTIO_VIDEO_F_RESOURCE_VIRTIO_OBJECT
    }

    fn read_config(&self, offset: u64, data: &mut [u8]) {
        let mut cfg = protocol::virtio_video_config {
            version: Le32::from(0),
            max_caps_length: Le32::from(1024), // Set a big number
            max_resp_length: Le32::from(1024), // Set a big number
        };
        copy_config(data, 0, cfg.as_mut_slice(), offset);
    }

    fn activate(
        &mut self,
        mem: GuestMemory,
        interrupt: Interrupt,
        mut queues: Vec<virtio::queue::Queue>,
        mut queue_evts: Vec<EventFd>,
    ) {
        if queues.len() != QUEUE_SIZES.len() {
            error!(
                "wrong number of queues are passed: expected {}, actual {}",
                queues.len(),
                QUEUE_SIZES.len()
            );
            return;
        }
        if queue_evts.len() != QUEUE_SIZES.len() {
            error!(
                "wrong number of event FDs are passed: expected {}, actual {}",
                queue_evts.len(),
                QUEUE_SIZES.len()
            );
        }

        let (self_kill_evt, kill_evt) = match EventFd::new().and_then(|e| Ok((e.try_clone()?, e))) {
            Ok(v) => v,
            Err(e) => {
                error!("failed to create kill EventFd pair: {:?}", e);
                return;
            }
        };
        self.kill_evt = Some(self_kill_evt);

        let cmd_queue = queues.remove(0);
        let cmd_evt = queue_evts.remove(0);
        let event_queue = queues.remove(0);
        let event_evt = queue_evts.remove(0);
        let resource_bridge = match self.resource_bridge.take() {
            Some(r) => r,
            None => {
                error!("no resource bridge is passed");
                return;
            }
        };
        let mut worker = Worker {
            interrupt,
            mem,
            cmd_evt,
            event_evt,
            kill_evt,
            resource_bridge,
        };
        let worker_result = match &self.device_type {
            VideoDeviceType::Decoder => thread::Builder::new()
                .name("virtio video decoder".to_owned())
                .spawn(move || {
                    let vda = libvda::VdaInstance::new(libvda::VdaImplType::Gavda)
                        .map_err(Error::LibvdaCreationFailed)?;
                    let device = decoder::Decoder::new(&vda);
                    worker.run(cmd_queue, event_queue, device)
                }),
            VideoDeviceType::Encoder => thread::Builder::new()
                .name("virtio video encoder".to_owned())
                .spawn(move || {
                    let device = encoder::Encoder::new();
                    worker.run(cmd_queue, event_queue, device)
                }),
        };
        if let Err(e) = worker_result {
            error!(
                "failed to spawn virtio_video worker for {:?}: {}",
                &self.device_type, e
            );
            return;
        }
    }
}