devices/virtio/video/
command.rs

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
7use std::convert::TryFrom;
8use std::convert::TryInto;
9use std::io;
10
11use base::error;
12use data_model::Le32;
13use enumn::N;
14use remain::sorted;
15use thiserror::Error as ThisError;
16
17use crate::virtio::video::control::*;
18use crate::virtio::video::format::*;
19use crate::virtio::video::params::Params;
20use crate::virtio::video::protocol::*;
21use crate::virtio::video::resource::ResourceType;
22use crate::virtio::video::resource::UnresolvedResourceEntry;
23use crate::virtio::Reader;
24
25/// An error indicating a failure while reading a request from the guest.
26#[sorted]
27#[derive(Debug, ThisError)]
28pub 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)]
45pub enum QueueType {
46    Input = VIRTIO_VIDEO_QUEUE_TYPE_INPUT,
47    Output = VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT,
48}
49impl_try_from_le32_for_enumn!(QueueType, "queue_type");
50
51#[derive(Debug)]
52pub 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
119impl VideoCmd {
120    /// Reads a request on virtqueue and construct a VideoCmd value.
121    pub fn from_reader(r: &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}