// 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. //! Data structures for commands of virtio video devices. use std::convert::{TryFrom, TryInto}; use std::fmt; use std::io; use data_model::Le32; use enumn::N; use sys_util::error; use crate::virtio::video::control::*; use crate::virtio::video::format::*; use crate::virtio::video::params::Params; use crate::virtio::video::protocol::*; use crate::virtio::Reader; /// An error indicating a failure while reading a request from the guest. #[derive(Debug)] pub enum ReadCmdError { /// Failure while reading an object. IoError(io::Error), /// Invalid arguement is passed, InvalidArgument, /// The type of the command was invalid. InvalidCmdType(u32), /// The type of the requested control was unsupported. UnsupportedCtrlType(u32), } impl fmt::Display for ReadCmdError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::ReadCmdError::*; match self { IoError(e) => write!(f, "failed to read an object: {}", e), InvalidArgument => write!(f, "invalid arguement is passed in command"), InvalidCmdType(t) => write!(f, "invalid command type: {}", t), UnsupportedCtrlType(t) => write!(f, "unsupported control type: {}", t), } } } impl std::error::Error for ReadCmdError {} impl From for ReadCmdError { fn from(e: io::Error) -> ReadCmdError { ReadCmdError::IoError(e) } } #[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"); pub enum VideoCmd { QueryCapability { queue_type: QueueType, }, StreamCreate { stream_id: u32, coded_format: Format, }, StreamDestroy { stream_id: u32, }, StreamDrain { stream_id: u32, }, ResourceCreate { stream_id: u32, queue_type: QueueType, resource_id: u32, plane_offsets: Vec, uuid: u128, }, ResourceQueue { stream_id: u32, queue_type: QueueType, resource_id: u32, timestamp: u64, data_sizes: Vec, }, ResourceDestroyAll { stream_id: u32, }, QueueClear { stream_id: u32, queue_type: QueueType, }, GetParams { stream_id: u32, queue_type: QueueType, }, SetParams { stream_id: u32, queue_type: QueueType, params: Params, }, 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<'a>) -> 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()?; if in_mem_type != VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT || out_mem_type != VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT { error!("mem_type must be VIRTIO_OBJECT"); return Err(InvalidArgument); } StreamCreate { stream_id: hdr.stream_id.into(), coded_format: coded_format.try_into()?, } } 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, .. } = r.read_obj()?; // Assume ChromeOS-specific requirements. if Into::::into(planes_layout) != VIRTIO_VIDEO_PLANES_LAYOUT_SINGLE_BUFFER { error!( "each buffer must be a single DMAbuf: {}", Into::::into(planes_layout), ); return Err(InvalidArgument); } let num_planes: u32 = num_planes.into(); if num_planes as usize > plane_offsets.len() { error!( "num_planes must not exceed {} but {}", plane_offsets.len(), num_planes ); return Err(InvalidArgument); } let plane_offsets = plane_offsets[0..num_planes as usize] .iter() .map(|x| Into::::into(*x)) .collect::>(); let virtio_video_object_entry { uuid } = r.read_obj()?; ResourceCreate { stream_id: hdr.stream_id.into(), queue_type: queue_type.try_into()?, resource_id: resource_id.into(), plane_offsets, uuid: u128::from_be_bytes(uuid), } } 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` should be ignored because destroy_all will affect both queues. // This field exists here by mistake. .. } = r.read_obj()?; ResourceDestroyAll { stream_id: hdr.stream_id.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()?, } } 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()?, } } VIRTIO_VIDEO_CMD_QUERY_CONTROL => { let body = r.read_obj::()?; let query_ctrl_type = match body.control.into() { VIRTIO_VIDEO_CONTROL_BITRATE => QueryCtrlType::Bitrate, 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_PROFILE => CtrlType::Profile, VIRTIO_VIDEO_CONTROL_LEVEL => CtrlType::Level, 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_PROFILE => CtrlVal::Profile( r.read_obj::()? .profile .try_into()?, ), VIRTIO_VIDEO_CONTROL_LEVEL => CtrlVal::Level( r.read_obj::()? .level .try_into()?, ), t => { return Err(ReadCmdError::UnsupportedCtrlType(t)); } }; SetControl { stream_id: hdr.stream_id.into(), ctrl_val, } } _ => return Err(ReadCmdError::InvalidCmdType(hdr.type_.into())), }) } }