// Copyright 2020 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. //! Data structures for commands of virtio video devices. use std::convert::TryFrom; use std::convert::TryInto; use std::io; use base::error; use data_model::Le32; use enumn::N; use remain::sorted; use thiserror::Error as ThisError; use crate::virtio::video::control::*; use crate::virtio::video::format::*; use crate::virtio::video::params::Params; use crate::virtio::video::protocol::*; use crate::virtio::video::resource::ResourceType; use crate::virtio::video::resource::UnresolvedResourceEntry; use crate::virtio::Reader; /// An error indicating a failure while reading a request from the guest. #[sorted] #[derive(Debug, ThisError)] pub enum ReadCmdError { /// Invalid argument is passed. #[error("invalid argument passed to command")] InvalidArgument, /// The type of the command was invalid. #[error("invalid command type: {0}")] InvalidCmdType(u32), /// Failed to read an object. #[error("failed to read object: {0}")] IoError(#[from] io::Error), /// The type of the requested control was unsupported. #[error("unsupported control type: {0}")] UnsupportedCtrlType(u32), } #[derive(PartialEq, Eq, PartialOrd, Ord, N, Clone, Copy, Debug)] #[repr(u32)] pub enum QueueType { Input = VIRTIO_VIDEO_QUEUE_TYPE_INPUT, Output = VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT, } impl_try_from_le32_for_enumn!(QueueType, "queue_type"); #[derive(Debug)] pub enum VideoCmd { QueryCapability { queue_type: QueueType, }, StreamCreate { stream_id: u32, coded_format: Format, input_resource_type: ResourceType, output_resource_type: ResourceType, }, StreamDestroy { stream_id: u32, }, StreamDrain { stream_id: u32, }, ResourceCreate { stream_id: u32, queue_type: QueueType, resource_id: u32, plane_offsets: Vec, /// The outer vector contains one entry per memory plane, whereas the inner vector contains /// all the memory entries that make a single plane (i.e. one for virtio objects, one or /// more for guest pages). plane_entries: Vec>, }, ResourceQueue { stream_id: u32, queue_type: QueueType, resource_id: u32, timestamp: u64, data_sizes: Vec, }, ResourceDestroyAll { stream_id: u32, queue_type: QueueType, }, QueueClear { stream_id: u32, queue_type: QueueType, }, GetParams { stream_id: u32, queue_type: QueueType, /// `true` if this command has been created from the GET_PARAMS_EXT guest command. is_ext: bool, }, SetParams { stream_id: u32, queue_type: QueueType, params: Params, /// `true` if this command has been created from the SET_PARAMS_EXT guest command. is_ext: bool, }, QueryControl { query_ctrl_type: QueryCtrlType, }, GetControl { stream_id: u32, ctrl_type: CtrlType, }, SetControl { stream_id: u32, ctrl_val: CtrlVal, }, } impl<'a> VideoCmd { /// Reads a request on virtqueue and construct a VideoCmd value. pub fn from_reader(r: &'a mut Reader) -> Result { use self::ReadCmdError::*; use self::VideoCmd::*; // Unlike structs in virtio_video.h in the kernel, our command structs in protocol.rs don't // have a field of `struct virtio_video_cmd_hdr`. So, we first read the header here and // a body below. let hdr = r.read_obj::()?; Ok(match hdr.type_.into() { VIRTIO_VIDEO_CMD_QUERY_CAPABILITY => { let virtio_video_query_capability { queue_type, .. } = r.read_obj()?; QueryCapability { queue_type: queue_type.try_into()?, } } VIRTIO_VIDEO_CMD_STREAM_CREATE => { let virtio_video_stream_create { in_mem_type, out_mem_type, coded_format, .. } = r.read_obj()?; let input_resource_type = match in_mem_type.into() { VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT => ResourceType::VirtioObject, VIRTIO_VIDEO_MEM_TYPE_GUEST_PAGES => ResourceType::GuestPages, m => { error!("Unsupported input resource memory type 0x{:x}!", m); return Err(InvalidArgument); } }; let output_resource_type = match out_mem_type.into() { VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT => ResourceType::VirtioObject, VIRTIO_VIDEO_MEM_TYPE_GUEST_PAGES => ResourceType::GuestPages, m => { error!("Unsupported output resource memory type 0x{:x}!", m); return Err(InvalidArgument); } }; StreamCreate { stream_id: hdr.stream_id.into(), coded_format: coded_format.try_into()?, input_resource_type, output_resource_type, } } VIRTIO_VIDEO_CMD_STREAM_DESTROY => { let virtio_video_stream_destroy { .. } = r.read_obj()?; StreamDestroy { stream_id: hdr.stream_id.into(), } } VIRTIO_VIDEO_CMD_STREAM_DRAIN => { let virtio_video_stream_drain { .. } = r.read_obj()?; StreamDrain { stream_id: hdr.stream_id.into(), } } VIRTIO_VIDEO_CMD_RESOURCE_CREATE => { let virtio_video_resource_create { queue_type, resource_id, planes_layout, num_planes, plane_offsets, num_entries, } = r.read_obj()?; // Assume ChromeOS-specific requirements. let planes_layout = Into::::into(planes_layout); if planes_layout != VIRTIO_VIDEO_PLANES_LAYOUT_SINGLE_BUFFER { error!("Only single-planar formats are supported for now"); return Err(InvalidArgument); } let num_planes = Into::::into(num_planes) as usize; if num_planes > plane_offsets.len() { error!( "num_planes is {} but shall not exceed {}", num_planes, plane_offsets.len(), ); return Err(InvalidArgument); } if planes_layout == VIRTIO_VIDEO_PLANES_LAYOUT_SINGLE_BUFFER && num_planes != 1 { error!( "Single-planar format specified but num_planes is {}", num_planes ); return Err(InvalidArgument); } let plane_offsets = plane_offsets[0..num_planes] .iter() .map(|x| Into::::into(*x)) .collect::>(); // Read all the entries for all the planes. let plane_entries = (0..num_planes as usize) .into_iter() .map(|i| { let num_entries: u32 = num_entries[i].into(); (0..num_entries) .into_iter() .map(|_| r.read_obj::()) .collect::, _>>() }) .collect::, _>>()?; ResourceCreate { stream_id: hdr.stream_id.into(), queue_type: queue_type.try_into()?, resource_id: resource_id.into(), plane_offsets, plane_entries, } } VIRTIO_VIDEO_CMD_RESOURCE_QUEUE => { let virtio_video_resource_queue { queue_type, resource_id, timestamp, num_data_sizes, data_sizes, .. } = r.read_obj()?; let num_data_sizes: u32 = num_data_sizes.into(); if num_data_sizes as usize > data_sizes.len() { return Err(InvalidArgument); } let data_sizes = data_sizes[0..num_data_sizes as usize] .iter() .map(|x| Into::::into(*x)) .collect::>(); ResourceQueue { stream_id: hdr.stream_id.into(), queue_type: queue_type.try_into()?, resource_id: resource_id.into(), timestamp: timestamp.into(), data_sizes, } } VIRTIO_VIDEO_CMD_RESOURCE_DESTROY_ALL => { let virtio_video_resource_destroy_all { queue_type, .. } = r.read_obj()?; ResourceDestroyAll { stream_id: hdr.stream_id.into(), queue_type: queue_type.try_into()?, } } VIRTIO_VIDEO_CMD_QUEUE_CLEAR => { let virtio_video_queue_clear { queue_type, .. } = r.read_obj()?; QueueClear { stream_id: hdr.stream_id.into(), queue_type: queue_type.try_into()?, } } VIRTIO_VIDEO_CMD_GET_PARAMS => { let virtio_video_get_params { queue_type, .. } = r.read_obj()?; GetParams { stream_id: hdr.stream_id.into(), queue_type: queue_type.try_into()?, is_ext: false, } } VIRTIO_VIDEO_CMD_SET_PARAMS => { let virtio_video_set_params { params } = r.read_obj()?; SetParams { stream_id: hdr.stream_id.into(), queue_type: params.queue_type.try_into()?, params: params.try_into()?, is_ext: false, } } VIRTIO_VIDEO_CMD_QUERY_CONTROL => { let body = r.read_obj::()?; let query_ctrl_type = match body.control.into() { VIRTIO_VIDEO_CONTROL_PROFILE => QueryCtrlType::Profile( r.read_obj::()? .format .try_into()?, ), VIRTIO_VIDEO_CONTROL_LEVEL => QueryCtrlType::Level( r.read_obj::()? .format .try_into()?, ), t => { return Err(ReadCmdError::UnsupportedCtrlType(t)); } }; QueryControl { query_ctrl_type } } VIRTIO_VIDEO_CMD_GET_CONTROL => { let virtio_video_get_control { control, .. } = r.read_obj()?; let ctrl_type = match control.into() { VIRTIO_VIDEO_CONTROL_BITRATE => CtrlType::Bitrate, VIRTIO_VIDEO_CONTROL_BITRATE_PEAK => CtrlType::BitratePeak, VIRTIO_VIDEO_CONTROL_BITRATE_MODE => CtrlType::BitrateMode, VIRTIO_VIDEO_CONTROL_PROFILE => CtrlType::Profile, VIRTIO_VIDEO_CONTROL_LEVEL => CtrlType::Level, VIRTIO_VIDEO_CONTROL_FORCE_KEYFRAME => CtrlType::ForceKeyframe, VIRTIO_VIDEO_CONTROL_PREPEND_SPSPPS_TO_IDR => CtrlType::PrependSpsPpsToIdr, t => { return Err(ReadCmdError::UnsupportedCtrlType(t)); } }; GetControl { stream_id: hdr.stream_id.into(), ctrl_type, } } VIRTIO_VIDEO_CMD_SET_CONTROL => { let virtio_video_set_control { control, .. } = r.read_obj()?; let ctrl_val = match control.into() { VIRTIO_VIDEO_CONTROL_BITRATE => CtrlVal::Bitrate( r.read_obj::()? .bitrate .into(), ), VIRTIO_VIDEO_CONTROL_BITRATE_PEAK => CtrlVal::BitratePeak( r.read_obj::()? .bitrate_peak .into(), ), VIRTIO_VIDEO_CONTROL_BITRATE_MODE => CtrlVal::BitrateMode( r.read_obj::()? .bitrate_mode .try_into()?, ), VIRTIO_VIDEO_CONTROL_PROFILE => CtrlVal::Profile( r.read_obj::()? .profile .try_into()?, ), VIRTIO_VIDEO_CONTROL_LEVEL => CtrlVal::Level( r.read_obj::()? .level .try_into()?, ), VIRTIO_VIDEO_CONTROL_FORCE_KEYFRAME => CtrlVal::ForceKeyframe, VIRTIO_VIDEO_CONTROL_PREPEND_SPSPPS_TO_IDR => CtrlVal::PrependSpsPpsToIdr( r.read_obj::()? .prepend_spspps_to_idr != 0, ), t => { return Err(ReadCmdError::UnsupportedCtrlType(t)); } }; SetControl { stream_id: hdr.stream_id.into(), ctrl_val, } } VIRTIO_VIDEO_CMD_GET_PARAMS_EXT => { let virtio_video_get_params_ext { queue_type, .. } = r.read_obj()?; GetParams { stream_id: hdr.stream_id.into(), queue_type: queue_type.try_into()?, is_ext: true, } } VIRTIO_VIDEO_CMD_SET_PARAMS_EXT => { let virtio_video_set_params_ext { params } = r.read_obj()?; SetParams { stream_id: hdr.stream_id.into(), queue_type: params.base.queue_type.try_into()?, params: params.try_into()?, is_ext: true, } } _ => return Err(ReadCmdError::InvalidCmdType(hdr.type_.into())), }) } }