1 // Copyright 2020 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 //! Data structures for commands of virtio video devices.
6 
7 use std::convert::{TryFrom, TryInto};
8 use std::fmt;
9 use std::io;
10 
11 use base::error;
12 use data_model::Le32;
13 use enumn::N;
14 
15 use crate::virtio::video::control::*;
16 use crate::virtio::video::format::*;
17 use crate::virtio::video::params::Params;
18 use crate::virtio::video::protocol::*;
19 use crate::virtio::Reader;
20 
21 /// An error indicating a failure while reading a request from the guest.
22 #[derive(Debug)]
23 pub enum ReadCmdError {
24     /// Failure while reading an object.
25     IoError(io::Error),
26     /// Invalid arguement is passed,
27     InvalidArgument,
28     /// The type of the command was invalid.
29     InvalidCmdType(u32),
30     /// The type of the requested control was unsupported.
31     UnsupportedCtrlType(u32),
32 }
33 
34 impl fmt::Display for ReadCmdError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result35     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
36         use self::ReadCmdError::*;
37         match self {
38             IoError(e) => write!(f, "failed to read an object: {}", e),
39             InvalidArgument => write!(f, "invalid arguement is passed in command"),
40             InvalidCmdType(t) => write!(f, "invalid command type: {}", t),
41             UnsupportedCtrlType(t) => write!(f, "unsupported control type: {}", t),
42         }
43     }
44 }
45 
46 impl std::error::Error for ReadCmdError {}
47 
48 impl From<io::Error> for ReadCmdError {
from(e: io::Error) -> ReadCmdError49     fn from(e: io::Error) -> ReadCmdError {
50         ReadCmdError::IoError(e)
51     }
52 }
53 
54 #[derive(PartialEq, Eq, PartialOrd, Ord, N, Clone, Copy, Debug)]
55 #[repr(u32)]
56 pub enum QueueType {
57     Input = VIRTIO_VIDEO_QUEUE_TYPE_INPUT,
58     Output = VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT,
59 }
60 impl_try_from_le32_for_enumn!(QueueType, "queue_type");
61 
62 #[derive(Debug)]
63 pub enum VideoCmd {
64     QueryCapability {
65         queue_type: QueueType,
66     },
67     StreamCreate {
68         stream_id: u32,
69         coded_format: Format,
70     },
71     StreamDestroy {
72         stream_id: u32,
73     },
74     StreamDrain {
75         stream_id: u32,
76     },
77     ResourceCreate {
78         stream_id: u32,
79         queue_type: QueueType,
80         resource_id: u32,
81         plane_offsets: Vec<u32>,
82         uuid: u128,
83     },
84     ResourceQueue {
85         stream_id: u32,
86         queue_type: QueueType,
87         resource_id: u32,
88         timestamp: u64,
89         data_sizes: Vec<u32>,
90     },
91     ResourceDestroyAll {
92         stream_id: u32,
93         queue_type: QueueType,
94     },
95     QueueClear {
96         stream_id: u32,
97         queue_type: QueueType,
98     },
99     GetParams {
100         stream_id: u32,
101         queue_type: QueueType,
102     },
103     SetParams {
104         stream_id: u32,
105         queue_type: QueueType,
106         params: Params,
107     },
108     QueryControl {
109         query_ctrl_type: QueryCtrlType,
110     },
111     GetControl {
112         stream_id: u32,
113         ctrl_type: CtrlType,
114     },
115     SetControl {
116         stream_id: u32,
117         ctrl_val: CtrlVal,
118     },
119 }
120 
121 impl<'a> VideoCmd {
122     /// Reads a request on virtqueue and construct a VideoCmd value.
from_reader(r: &'a mut Reader) -> Result<Self, ReadCmdError>123     pub fn from_reader(r: &'a mut Reader) -> Result<Self, ReadCmdError> {
124         use self::ReadCmdError::*;
125         use self::VideoCmd::*;
126 
127         // Unlike structs in virtio_video.h in the kernel, our command structs in protocol.rs don't
128         // have a field of `struct virtio_video_cmd_hdr`. So, we first read the header here and
129         // a body below.
130         let hdr = r.read_obj::<virtio_video_cmd_hdr>()?;
131 
132         Ok(match hdr.type_.into() {
133             VIRTIO_VIDEO_CMD_QUERY_CAPABILITY => {
134                 let virtio_video_query_capability { queue_type, .. } = r.read_obj()?;
135                 QueryCapability {
136                     queue_type: queue_type.try_into()?,
137                 }
138             }
139             VIRTIO_VIDEO_CMD_STREAM_CREATE => {
140                 let virtio_video_stream_create {
141                     in_mem_type,
142                     out_mem_type,
143                     coded_format,
144                     ..
145                 } = r.read_obj()?;
146 
147                 if in_mem_type != VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT
148                     || out_mem_type != VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT
149                 {
150                     error!("mem_type must be VIRTIO_OBJECT");
151                     return Err(InvalidArgument);
152                 }
153                 StreamCreate {
154                     stream_id: hdr.stream_id.into(),
155                     coded_format: coded_format.try_into()?,
156                 }
157             }
158             VIRTIO_VIDEO_CMD_STREAM_DESTROY => {
159                 let virtio_video_stream_destroy { .. } = r.read_obj()?;
160                 StreamDestroy {
161                     stream_id: hdr.stream_id.into(),
162                 }
163             }
164             VIRTIO_VIDEO_CMD_STREAM_DRAIN => {
165                 let virtio_video_stream_drain { .. } = r.read_obj()?;
166                 StreamDrain {
167                     stream_id: hdr.stream_id.into(),
168                 }
169             }
170             VIRTIO_VIDEO_CMD_RESOURCE_CREATE => {
171                 let virtio_video_resource_create {
172                     queue_type,
173                     resource_id,
174                     planes_layout,
175                     num_planes,
176                     plane_offsets,
177                     ..
178                 } = r.read_obj()?;
179 
180                 // Assume ChromeOS-specific requirements.
181                 if Into::<u32>::into(planes_layout) != VIRTIO_VIDEO_PLANES_LAYOUT_SINGLE_BUFFER {
182                     error!(
183                         "each buffer must be a single DMAbuf: {}",
184                         Into::<u32>::into(planes_layout),
185                     );
186                     return Err(InvalidArgument);
187                 }
188 
189                 let num_planes: u32 = num_planes.into();
190                 if num_planes as usize > plane_offsets.len() {
191                     error!(
192                         "num_planes must not exceed {} but {}",
193                         plane_offsets.len(),
194                         num_planes
195                     );
196                     return Err(InvalidArgument);
197                 }
198                 let plane_offsets = plane_offsets[0..num_planes as usize]
199                     .iter()
200                     .map(|x| Into::<u32>::into(*x))
201                     .collect::<Vec<u32>>();
202 
203                 let virtio_video_object_entry { uuid } = r.read_obj()?;
204 
205                 ResourceCreate {
206                     stream_id: hdr.stream_id.into(),
207                     queue_type: queue_type.try_into()?,
208                     resource_id: resource_id.into(),
209                     plane_offsets,
210                     uuid: u128::from_be_bytes(uuid),
211                 }
212             }
213             VIRTIO_VIDEO_CMD_RESOURCE_QUEUE => {
214                 let virtio_video_resource_queue {
215                     queue_type,
216                     resource_id,
217                     timestamp,
218                     num_data_sizes,
219                     data_sizes,
220                     ..
221                 } = r.read_obj()?;
222 
223                 let num_data_sizes: u32 = num_data_sizes.into();
224                 if num_data_sizes as usize > data_sizes.len() {
225                     return Err(InvalidArgument);
226                 }
227                 let data_sizes = data_sizes[0..num_data_sizes as usize]
228                     .iter()
229                     .map(|x| Into::<u32>::into(*x))
230                     .collect::<Vec<u32>>();
231                 ResourceQueue {
232                     stream_id: hdr.stream_id.into(),
233                     queue_type: queue_type.try_into()?,
234                     resource_id: resource_id.into(),
235                     timestamp: timestamp.into(),
236                     data_sizes,
237                 }
238             }
239             VIRTIO_VIDEO_CMD_RESOURCE_DESTROY_ALL => {
240                 let virtio_video_resource_destroy_all { queue_type, .. } = r.read_obj()?;
241                 ResourceDestroyAll {
242                     stream_id: hdr.stream_id.into(),
243                     queue_type: queue_type.try_into()?,
244                 }
245             }
246             VIRTIO_VIDEO_CMD_QUEUE_CLEAR => {
247                 let virtio_video_queue_clear { queue_type, .. } = r.read_obj()?;
248                 QueueClear {
249                     stream_id: hdr.stream_id.into(),
250                     queue_type: queue_type.try_into()?,
251                 }
252             }
253             VIRTIO_VIDEO_CMD_GET_PARAMS => {
254                 let virtio_video_get_params { queue_type, .. } = r.read_obj()?;
255                 GetParams {
256                     stream_id: hdr.stream_id.into(),
257                     queue_type: queue_type.try_into()?,
258                 }
259             }
260             VIRTIO_VIDEO_CMD_SET_PARAMS => {
261                 let virtio_video_set_params { params } = r.read_obj()?;
262                 SetParams {
263                     stream_id: hdr.stream_id.into(),
264                     queue_type: params.queue_type.try_into()?,
265                     params: params.try_into()?,
266                 }
267             }
268             VIRTIO_VIDEO_CMD_QUERY_CONTROL => {
269                 let body = r.read_obj::<virtio_video_query_control>()?;
270                 let query_ctrl_type = match body.control.into() {
271                     VIRTIO_VIDEO_CONTROL_PROFILE => QueryCtrlType::Profile(
272                         r.read_obj::<virtio_video_query_control_profile>()?
273                             .format
274                             .try_into()?,
275                     ),
276                     VIRTIO_VIDEO_CONTROL_LEVEL => QueryCtrlType::Level(
277                         r.read_obj::<virtio_video_query_control_level>()?
278                             .format
279                             .try_into()?,
280                     ),
281                     t => {
282                         return Err(ReadCmdError::UnsupportedCtrlType(t));
283                     }
284                 };
285                 QueryControl { query_ctrl_type }
286             }
287             VIRTIO_VIDEO_CMD_GET_CONTROL => {
288                 let virtio_video_get_control { control, .. } = r.read_obj()?;
289                 let ctrl_type = match control.into() {
290                     VIRTIO_VIDEO_CONTROL_BITRATE => CtrlType::Bitrate,
291                     VIRTIO_VIDEO_CONTROL_PROFILE => CtrlType::Profile,
292                     VIRTIO_VIDEO_CONTROL_LEVEL => CtrlType::Level,
293                     VIRTIO_VIDEO_CONTROL_FORCE_KEYFRAME => CtrlType::ForceKeyframe,
294                     t => {
295                         return Err(ReadCmdError::UnsupportedCtrlType(t));
296                     }
297                 };
298                 GetControl {
299                     stream_id: hdr.stream_id.into(),
300                     ctrl_type,
301                 }
302             }
303             VIRTIO_VIDEO_CMD_SET_CONTROL => {
304                 let virtio_video_set_control { control, .. } = r.read_obj()?;
305                 let ctrl_val = match control.into() {
306                     VIRTIO_VIDEO_CONTROL_BITRATE => CtrlVal::Bitrate(
307                         r.read_obj::<virtio_video_control_val_bitrate>()?
308                             .bitrate
309                             .into(),
310                     ),
311                     VIRTIO_VIDEO_CONTROL_PROFILE => CtrlVal::Profile(
312                         r.read_obj::<virtio_video_control_val_profile>()?
313                             .profile
314                             .try_into()?,
315                     ),
316                     VIRTIO_VIDEO_CONTROL_LEVEL => CtrlVal::Level(
317                         r.read_obj::<virtio_video_control_val_level>()?
318                             .level
319                             .try_into()?,
320                     ),
321                     VIRTIO_VIDEO_CONTROL_FORCE_KEYFRAME => CtrlVal::ForceKeyframe(),
322                     t => {
323                         return Err(ReadCmdError::UnsupportedCtrlType(t));
324                     }
325                 };
326                 SetControl {
327                     stream_id: hdr.stream_id.into(),
328                     ctrl_val,
329                 }
330             }
331             _ => return Err(ReadCmdError::InvalidCmdType(hdr.type_.into())),
332         })
333     }
334 }
335