xref: /aosp_15_r20/external/crosvm/devices/src/virtio/video/command.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2020 The ChromiumOS Authors
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;
8 use std::convert::TryInto;
9 use std::io;
10 
11 use base::error;
12 use data_model::Le32;
13 use enumn::N;
14 use remain::sorted;
15 use thiserror::Error as ThisError;
16 
17 use crate::virtio::video::control::*;
18 use crate::virtio::video::format::*;
19 use crate::virtio::video::params::Params;
20 use crate::virtio::video::protocol::*;
21 use crate::virtio::video::resource::ResourceType;
22 use crate::virtio::video::resource::UnresolvedResourceEntry;
23 use crate::virtio::Reader;
24 
25 /// An error indicating a failure while reading a request from the guest.
26 #[sorted]
27 #[derive(Debug, ThisError)]
28 pub enum ReadCmdError {
29     /// Invalid argument is passed.
30     #[error("invalid argument passed to command")]
31     InvalidArgument,
32     /// The type of the command was invalid.
33     #[error("invalid command type: {0}")]
34     InvalidCmdType(u32),
35     /// Failed to read an object.
36     #[error("failed to read object: {0}")]
37     IoError(#[from] io::Error),
38     /// The type of the requested control was unsupported.
39     #[error("unsupported control type: {0}")]
40     UnsupportedCtrlType(u32),
41 }
42 
43 #[derive(PartialEq, Eq, PartialOrd, Ord, N, Clone, Copy, Debug)]
44 #[repr(u32)]
45 pub enum QueueType {
46     Input = VIRTIO_VIDEO_QUEUE_TYPE_INPUT,
47     Output = VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT,
48 }
49 impl_try_from_le32_for_enumn!(QueueType, "queue_type");
50 
51 #[derive(Debug)]
52 pub enum VideoCmd {
53     QueryCapability {
54         queue_type: QueueType,
55     },
56     StreamCreate {
57         stream_id: u32,
58         coded_format: Format,
59         input_resource_type: ResourceType,
60         output_resource_type: ResourceType,
61     },
62     StreamDestroy {
63         stream_id: u32,
64     },
65     StreamDrain {
66         stream_id: u32,
67     },
68     ResourceCreate {
69         stream_id: u32,
70         queue_type: QueueType,
71         resource_id: u32,
72         plane_offsets: Vec<u32>,
73         /// The outer vector contains one entry per memory plane, whereas the inner vector contains
74         /// all the memory entries that make a single plane (i.e. one for virtio objects, one or
75         /// more for guest pages).
76         plane_entries: Vec<Vec<UnresolvedResourceEntry>>,
77     },
78     ResourceQueue {
79         stream_id: u32,
80         queue_type: QueueType,
81         resource_id: u32,
82         timestamp: u64,
83         data_sizes: Vec<u32>,
84     },
85     ResourceDestroyAll {
86         stream_id: u32,
87         queue_type: QueueType,
88     },
89     QueueClear {
90         stream_id: u32,
91         queue_type: QueueType,
92     },
93     GetParams {
94         stream_id: u32,
95         queue_type: QueueType,
96         /// `true` if this command has been created from the GET_PARAMS_EXT guest command.
97         is_ext: bool,
98     },
99     SetParams {
100         stream_id: u32,
101         queue_type: QueueType,
102         params: Params,
103         /// `true` if this command has been created from the SET_PARAMS_EXT guest command.
104         is_ext: bool,
105     },
106     QueryControl {
107         query_ctrl_type: QueryCtrlType,
108     },
109     GetControl {
110         stream_id: u32,
111         ctrl_type: CtrlType,
112     },
113     SetControl {
114         stream_id: u32,
115         ctrl_val: CtrlVal,
116     },
117 }
118 
119 impl<'a> VideoCmd {
120     /// Reads a request on virtqueue and construct a VideoCmd value.
from_reader(r: &'a mut Reader) -> Result<Self, ReadCmdError>121     pub fn from_reader(r: &'a mut Reader) -> Result<Self, ReadCmdError> {
122         use self::ReadCmdError::*;
123         use self::VideoCmd::*;
124 
125         // Unlike structs in virtio_video.h in the kernel, our command structs in protocol.rs don't
126         // have a field of `struct virtio_video_cmd_hdr`. So, we first read the header here and
127         // a body below.
128         let hdr = r.read_obj::<virtio_video_cmd_hdr>()?;
129 
130         Ok(match hdr.type_.into() {
131             VIRTIO_VIDEO_CMD_QUERY_CAPABILITY => {
132                 let virtio_video_query_capability { queue_type, .. } = r.read_obj()?;
133                 QueryCapability {
134                     queue_type: queue_type.try_into()?,
135                 }
136             }
137             VIRTIO_VIDEO_CMD_STREAM_CREATE => {
138                 let virtio_video_stream_create {
139                     in_mem_type,
140                     out_mem_type,
141                     coded_format,
142                     ..
143                 } = r.read_obj()?;
144 
145                 let input_resource_type = match in_mem_type.into() {
146                     VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT => ResourceType::VirtioObject,
147                     VIRTIO_VIDEO_MEM_TYPE_GUEST_PAGES => ResourceType::GuestPages,
148                     m => {
149                         error!("Unsupported input resource memory type 0x{:x}!", m);
150                         return Err(InvalidArgument);
151                     }
152                 };
153 
154                 let output_resource_type = match out_mem_type.into() {
155                     VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT => ResourceType::VirtioObject,
156                     VIRTIO_VIDEO_MEM_TYPE_GUEST_PAGES => ResourceType::GuestPages,
157                     m => {
158                         error!("Unsupported output resource memory type 0x{:x}!", m);
159                         return Err(InvalidArgument);
160                     }
161                 };
162 
163                 StreamCreate {
164                     stream_id: hdr.stream_id.into(),
165                     coded_format: coded_format.try_into()?,
166                     input_resource_type,
167                     output_resource_type,
168                 }
169             }
170             VIRTIO_VIDEO_CMD_STREAM_DESTROY => {
171                 let virtio_video_stream_destroy { .. } = r.read_obj()?;
172                 StreamDestroy {
173                     stream_id: hdr.stream_id.into(),
174                 }
175             }
176             VIRTIO_VIDEO_CMD_STREAM_DRAIN => {
177                 let virtio_video_stream_drain { .. } = r.read_obj()?;
178                 StreamDrain {
179                     stream_id: hdr.stream_id.into(),
180                 }
181             }
182             VIRTIO_VIDEO_CMD_RESOURCE_CREATE => {
183                 let virtio_video_resource_create {
184                     queue_type,
185                     resource_id,
186                     planes_layout,
187                     num_planes,
188                     plane_offsets,
189                     num_entries,
190                 } = r.read_obj()?;
191 
192                 // Assume ChromeOS-specific requirements.
193                 let planes_layout = Into::<u32>::into(planes_layout);
194                 if planes_layout != VIRTIO_VIDEO_PLANES_LAYOUT_SINGLE_BUFFER {
195                     error!("Only single-planar formats are supported for now");
196                     return Err(InvalidArgument);
197                 }
198 
199                 let num_planes = Into::<u32>::into(num_planes) as usize;
200                 if num_planes > plane_offsets.len() {
201                     error!(
202                         "num_planes is {} but shall not exceed {}",
203                         num_planes,
204                         plane_offsets.len(),
205                     );
206                     return Err(InvalidArgument);
207                 }
208                 if planes_layout == VIRTIO_VIDEO_PLANES_LAYOUT_SINGLE_BUFFER && num_planes != 1 {
209                     error!(
210                         "Single-planar format specified but num_planes is {}",
211                         num_planes
212                     );
213                     return Err(InvalidArgument);
214                 }
215 
216                 let plane_offsets = plane_offsets[0..num_planes]
217                     .iter()
218                     .map(|x| Into::<u32>::into(*x))
219                     .collect::<Vec<u32>>();
220 
221                 // Read all the entries for all the planes.
222                 let plane_entries = (0..num_planes)
223                     .map(|i| {
224                         let num_entries: u32 = num_entries[i].into();
225                         (0..num_entries)
226                             .map(|_| r.read_obj::<UnresolvedResourceEntry>())
227                             .collect::<Result<Vec<_>, _>>()
228                     })
229                     .collect::<Result<Vec<_>, _>>()?;
230 
231                 ResourceCreate {
232                     stream_id: hdr.stream_id.into(),
233                     queue_type: queue_type.try_into()?,
234                     resource_id: resource_id.into(),
235                     plane_offsets,
236                     plane_entries,
237                 }
238             }
239             VIRTIO_VIDEO_CMD_RESOURCE_QUEUE => {
240                 let virtio_video_resource_queue {
241                     queue_type,
242                     resource_id,
243                     timestamp,
244                     num_data_sizes,
245                     data_sizes,
246                     ..
247                 } = r.read_obj()?;
248 
249                 let num_data_sizes: u32 = num_data_sizes.into();
250                 if num_data_sizes as usize > data_sizes.len() {
251                     return Err(InvalidArgument);
252                 }
253                 let data_sizes = data_sizes[0..num_data_sizes as usize]
254                     .iter()
255                     .map(|x| Into::<u32>::into(*x))
256                     .collect::<Vec<u32>>();
257                 ResourceQueue {
258                     stream_id: hdr.stream_id.into(),
259                     queue_type: queue_type.try_into()?,
260                     resource_id: resource_id.into(),
261                     timestamp: timestamp.into(),
262                     data_sizes,
263                 }
264             }
265             VIRTIO_VIDEO_CMD_RESOURCE_DESTROY_ALL => {
266                 let virtio_video_resource_destroy_all { queue_type, .. } = r.read_obj()?;
267                 ResourceDestroyAll {
268                     stream_id: hdr.stream_id.into(),
269                     queue_type: queue_type.try_into()?,
270                 }
271             }
272             VIRTIO_VIDEO_CMD_QUEUE_CLEAR => {
273                 let virtio_video_queue_clear { queue_type, .. } = r.read_obj()?;
274                 QueueClear {
275                     stream_id: hdr.stream_id.into(),
276                     queue_type: queue_type.try_into()?,
277                 }
278             }
279             VIRTIO_VIDEO_CMD_GET_PARAMS => {
280                 let virtio_video_get_params { queue_type, .. } = r.read_obj()?;
281                 GetParams {
282                     stream_id: hdr.stream_id.into(),
283                     queue_type: queue_type.try_into()?,
284                     is_ext: false,
285                 }
286             }
287             VIRTIO_VIDEO_CMD_SET_PARAMS => {
288                 let virtio_video_set_params { params } = r.read_obj()?;
289                 SetParams {
290                     stream_id: hdr.stream_id.into(),
291                     queue_type: params.queue_type.try_into()?,
292                     params: params.try_into()?,
293                     is_ext: false,
294                 }
295             }
296             VIRTIO_VIDEO_CMD_QUERY_CONTROL => {
297                 let body = r.read_obj::<virtio_video_query_control>()?;
298                 let query_ctrl_type = match body.control.into() {
299                     VIRTIO_VIDEO_CONTROL_PROFILE => QueryCtrlType::Profile(
300                         r.read_obj::<virtio_video_query_control_profile>()?
301                             .format
302                             .try_into()?,
303                     ),
304                     VIRTIO_VIDEO_CONTROL_LEVEL => QueryCtrlType::Level(
305                         r.read_obj::<virtio_video_query_control_level>()?
306                             .format
307                             .try_into()?,
308                     ),
309                     t => {
310                         return Err(ReadCmdError::UnsupportedCtrlType(t));
311                     }
312                 };
313                 QueryControl { query_ctrl_type }
314             }
315             VIRTIO_VIDEO_CMD_GET_CONTROL => {
316                 let virtio_video_get_control { control, .. } = r.read_obj()?;
317                 let ctrl_type = match control.into() {
318                     VIRTIO_VIDEO_CONTROL_BITRATE => CtrlType::Bitrate,
319                     VIRTIO_VIDEO_CONTROL_BITRATE_PEAK => CtrlType::BitratePeak,
320                     VIRTIO_VIDEO_CONTROL_BITRATE_MODE => CtrlType::BitrateMode,
321                     VIRTIO_VIDEO_CONTROL_PROFILE => CtrlType::Profile,
322                     VIRTIO_VIDEO_CONTROL_LEVEL => CtrlType::Level,
323                     VIRTIO_VIDEO_CONTROL_FORCE_KEYFRAME => CtrlType::ForceKeyframe,
324                     VIRTIO_VIDEO_CONTROL_PREPEND_SPSPPS_TO_IDR => CtrlType::PrependSpsPpsToIdr,
325                     t => {
326                         return Err(ReadCmdError::UnsupportedCtrlType(t));
327                     }
328                 };
329                 GetControl {
330                     stream_id: hdr.stream_id.into(),
331                     ctrl_type,
332                 }
333             }
334             VIRTIO_VIDEO_CMD_SET_CONTROL => {
335                 let virtio_video_set_control { control, .. } = r.read_obj()?;
336                 let ctrl_val = match control.into() {
337                     VIRTIO_VIDEO_CONTROL_BITRATE => CtrlVal::Bitrate(
338                         r.read_obj::<virtio_video_control_val_bitrate>()?
339                             .bitrate
340                             .into(),
341                     ),
342                     VIRTIO_VIDEO_CONTROL_BITRATE_PEAK => CtrlVal::BitratePeak(
343                         r.read_obj::<virtio_video_control_val_bitrate_peak>()?
344                             .bitrate_peak
345                             .into(),
346                     ),
347                     VIRTIO_VIDEO_CONTROL_BITRATE_MODE => CtrlVal::BitrateMode(
348                         r.read_obj::<virtio_video_control_val_bitrate_mode>()?
349                             .bitrate_mode
350                             .try_into()?,
351                     ),
352                     VIRTIO_VIDEO_CONTROL_PROFILE => CtrlVal::Profile(
353                         r.read_obj::<virtio_video_control_val_profile>()?
354                             .profile
355                             .try_into()?,
356                     ),
357                     VIRTIO_VIDEO_CONTROL_LEVEL => CtrlVal::Level(
358                         r.read_obj::<virtio_video_control_val_level>()?
359                             .level
360                             .try_into()?,
361                     ),
362                     VIRTIO_VIDEO_CONTROL_FORCE_KEYFRAME => CtrlVal::ForceKeyframe,
363                     VIRTIO_VIDEO_CONTROL_PREPEND_SPSPPS_TO_IDR => CtrlVal::PrependSpsPpsToIdr(
364                         r.read_obj::<virtio_video_control_val_prepend_spspps_to_idr>()?
365                             .prepend_spspps_to_idr
366                             != 0,
367                     ),
368                     t => {
369                         return Err(ReadCmdError::UnsupportedCtrlType(t));
370                     }
371                 };
372                 SetControl {
373                     stream_id: hdr.stream_id.into(),
374                     ctrl_val,
375                 }
376             }
377             VIRTIO_VIDEO_CMD_GET_PARAMS_EXT => {
378                 let virtio_video_get_params_ext { queue_type, .. } = r.read_obj()?;
379                 GetParams {
380                     stream_id: hdr.stream_id.into(),
381                     queue_type: queue_type.try_into()?,
382                     is_ext: true,
383                 }
384             }
385             VIRTIO_VIDEO_CMD_SET_PARAMS_EXT => {
386                 let virtio_video_set_params_ext { params } = r.read_obj()?;
387                 SetParams {
388                     stream_id: hdr.stream_id.into(),
389                     queue_type: params.base.queue_type.try_into()?,
390                     params: params.try_into()?,
391                     is_ext: true,
392                 }
393             }
394             _ => return Err(ReadCmdError::InvalidCmdType(hdr.type_.into())),
395         })
396     }
397 }
398