Skip to main content

virtio_media/
ioctl.rs

1// Copyright 2024 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
5use std::io::Result as IoResult;
6
7use v4l2r::bindings::v4l2_audio;
8use v4l2r::bindings::v4l2_audioout;
9use v4l2r::bindings::v4l2_buffer;
10use v4l2r::bindings::v4l2_control;
11use v4l2r::bindings::v4l2_create_buffers;
12use v4l2r::bindings::v4l2_decoder_cmd;
13use v4l2r::bindings::v4l2_dv_timings;
14use v4l2r::bindings::v4l2_dv_timings_cap;
15use v4l2r::bindings::v4l2_enc_idx;
16use v4l2r::bindings::v4l2_encoder_cmd;
17use v4l2r::bindings::v4l2_enum_dv_timings;
18use v4l2r::bindings::v4l2_event_subscription;
19use v4l2r::bindings::v4l2_ext_control;
20use v4l2r::bindings::v4l2_ext_controls;
21use v4l2r::bindings::v4l2_fmtdesc;
22use v4l2r::bindings::v4l2_format;
23use v4l2r::bindings::v4l2_frequency;
24use v4l2r::bindings::v4l2_frequency_band;
25use v4l2r::bindings::v4l2_frmivalenum;
26use v4l2r::bindings::v4l2_frmsizeenum;
27use v4l2r::bindings::v4l2_input;
28use v4l2r::bindings::v4l2_modulator;
29use v4l2r::bindings::v4l2_output;
30use v4l2r::bindings::v4l2_plane;
31use v4l2r::bindings::v4l2_query_ext_ctrl;
32use v4l2r::bindings::v4l2_queryctrl;
33use v4l2r::bindings::v4l2_querymenu;
34use v4l2r::bindings::v4l2_rect;
35use v4l2r::bindings::v4l2_requestbuffers;
36use v4l2r::bindings::v4l2_selection;
37use v4l2r::bindings::v4l2_standard;
38use v4l2r::bindings::v4l2_std_id;
39use v4l2r::bindings::v4l2_streamparm;
40use v4l2r::bindings::v4l2_tuner;
41use v4l2r::ioctl::AudioMode;
42use v4l2r::ioctl::CtrlId;
43use v4l2r::ioctl::CtrlWhich;
44use v4l2r::ioctl::EventType as V4l2EventType;
45use v4l2r::ioctl::MemoryConsistency;
46use v4l2r::ioctl::QueryCtrlFlags;
47use v4l2r::ioctl::SelectionFlags;
48use v4l2r::ioctl::SelectionTarget;
49use v4l2r::ioctl::SelectionType;
50use v4l2r::ioctl::SubscribeEventFlags;
51use v4l2r::ioctl::TunerMode;
52use v4l2r::ioctl::TunerTransmissionFlags;
53use v4l2r::ioctl::TunerType;
54use v4l2r::ioctl::UncheckedV4l2Buffer;
55use v4l2r::ioctl::V4l2Buffer;
56use v4l2r::ioctl::V4l2PlanesWithBacking;
57use v4l2r::memory::MemoryType;
58use v4l2r::QueueDirection;
59use v4l2r::QueueType;
60
61use crate::io::ReadFromDescriptorChain;
62use crate::io::VmediaType;
63use crate::io::WriteToDescriptorChain;
64use crate::protocol::RespHeader;
65use crate::protocol::SgEntry;
66use crate::protocol::V4l2Ioctl;
67
68/// Reads a SG list of guest physical addresses passed from the driver and returns it.
69fn get_userptr_regions<R: ReadFromDescriptorChain>(
70    r: &mut R,
71    size: usize,
72) -> anyhow::Result<Vec<SgEntry>> {
73    let mut bytes_taken = 0;
74    let mut res = Vec::new();
75
76    while bytes_taken < size {
77        let sg_entry = r.read_obj::<SgEntry>()?;
78        bytes_taken += sg_entry.len as usize;
79        res.push(sg_entry);
80    }
81
82    Ok(res)
83}
84
85/// Local trait for reading simple or complex objects from a reader, e.g. the device-readable
86/// section of a descriptor chain.
87trait FromDescriptorChain {
88    fn read_from_chain<R: ReadFromDescriptorChain>(reader: &mut R) -> std::io::Result<Self>
89    where
90        Self: Sized;
91}
92
93/// Implementation for simple objects that can be returned as-is after their endianness is
94/// fixed.
95impl<T> FromDescriptorChain for T
96where
97    T: VmediaType,
98{
99    fn read_from_chain<R: ReadFromDescriptorChain>(reader: &mut R) -> std::io::Result<Self> {
100        reader.read_obj()
101    }
102}
103
104/// Implementation to easily read a `v4l2_buffer` of `USERPTR` memory type and its associated
105/// guest-side buffers from a descriptor chain.
106impl FromDescriptorChain for (V4l2Buffer, Vec<Vec<SgEntry>>) {
107    fn read_from_chain<R: ReadFromDescriptorChain>(reader: &mut R) -> IoResult<Self>
108    where
109        Self: Sized,
110    {
111        let v4l2_buffer = reader.read_obj::<v4l2_buffer>()?;
112        let queue = match QueueType::n(v4l2_buffer.type_) {
113            Some(queue) => queue,
114            None => return Err(std::io::ErrorKind::InvalidData.into()),
115        };
116
117        let v4l2_planes = if queue.is_multiplanar() && v4l2_buffer.length > 0 {
118            if v4l2_buffer.length > v4l2r::bindings::VIDEO_MAX_PLANES {
119                return Err(std::io::ErrorKind::InvalidData.into());
120            }
121
122            let planes: [v4l2r::bindings::v4l2_plane; v4l2r::bindings::VIDEO_MAX_PLANES as usize] =
123                (0..v4l2_buffer.length as usize)
124                    .map(|_| reader.read_obj::<v4l2_plane>())
125                    .collect::<IoResult<Vec<_>>>()?
126                    .into_iter()
127                    .chain(std::iter::repeat(Default::default()))
128                    .take(v4l2r::bindings::VIDEO_MAX_PLANES as usize)
129                    .collect::<Vec<_>>()
130                    .try_into()
131                    .map_err(|_| std::io::Error::from(std::io::ErrorKind::InvalidData))?;
132            Some(planes)
133        } else {
134            None
135        };
136
137        let v4l2_buffer = V4l2Buffer::try_from(UncheckedV4l2Buffer(v4l2_buffer, v4l2_planes))
138            .map_err(|_| std::io::Error::from(std::io::ErrorKind::InvalidData))?;
139
140        // Read the `MemRegion`s of all planes if the buffer is `USERPTR`.
141        let guest_regions = if let V4l2PlanesWithBacking::UserPtr(planes) =
142            v4l2_buffer.planes_with_backing_iter()
143        {
144            planes
145                .filter(|p| *p.length > 0)
146                .map(|p| {
147                    get_userptr_regions(reader, *p.length as usize)
148                        .map_err(|_| std::io::ErrorKind::InvalidData.into())
149                })
150                .collect::<IoResult<Vec<_>>>()?
151        } else {
152            vec![]
153        };
154
155        Ok((v4l2_buffer, guest_regions))
156    }
157}
158
159/// Implementation to easily read a `v4l2_ext_controls` struct, its array of controls, and the SG
160/// list of the buffers pointed to by the controls from a descriptor chain.
161impl FromDescriptorChain for (v4l2_ext_controls, Vec<v4l2_ext_control>, Vec<Vec<SgEntry>>) {
162    fn read_from_chain<R: ReadFromDescriptorChain>(reader: &mut R) -> std::io::Result<Self>
163    where
164        Self: Sized,
165    {
166        let ctrls = reader.read_obj::<v4l2_ext_controls>()?;
167
168        let ctrl_array = (0..ctrls.count)
169            .map(|_| reader.read_obj::<v4l2_ext_control>())
170            .collect::<IoResult<Vec<_>>>()?;
171
172        // Read all the payloads.
173        let mem_regions = ctrl_array
174            .iter()
175            .filter(|ctrl| ctrl.size > 0)
176            .map(|ctrl| {
177                get_userptr_regions(reader, ctrl.size as usize)
178                    .map_err(|_| std::io::Error::from(std::io::ErrorKind::InvalidData))
179            })
180            .collect::<IoResult<Vec<_>>>()?;
181
182        Ok((ctrls, ctrl_array, mem_regions))
183    }
184}
185
186/// Local trait for writing simple or complex objects to a writer, e.g. the device-writable section
187/// of a descriptor chain.
188trait ToDescriptorChain {
189    fn write_to_chain<W: WriteToDescriptorChain>(self, writer: &mut W) -> std::io::Result<()>;
190}
191
192/// Implementation for simple objects that can be written as-is after their endianness is
193/// fixed.
194impl<T> ToDescriptorChain for T
195where
196    T: VmediaType,
197{
198    fn write_to_chain<W: WriteToDescriptorChain>(self, writer: &mut W) -> std::io::Result<()> {
199        writer.write_obj(self)
200    }
201}
202
203/// Implementation to easily write a `v4l2_buffer` to a descriptor chain, while ensuring the number
204/// of planes written is not larger than a limit (i.e. the maximum number of planes that the
205/// descriptor chain can receive).
206impl ToDescriptorChain for (V4l2Buffer, usize) {
207    fn write_to_chain<W: WriteToDescriptorChain>(self, writer: &mut W) -> std::io::Result<()> {
208        let mut v4l2_buffer = *self.0.as_v4l2_buffer();
209        // If the buffer is multiplanar, nullify the `planes` pointer to avoid leaking host
210        // addresses.
211        if self.0.queue().is_multiplanar() {
212            v4l2_buffer.m.planes = std::ptr::null_mut();
213        }
214        writer.write_obj(v4l2_buffer)?;
215
216        // Write plane information if the buffer is multiplanar. Limit the number of planes to the
217        // upper bound we were given.
218        for plane in self.0.as_v4l2_planes().iter().take(self.1) {
219            writer.write_obj(*plane)?;
220        }
221
222        Ok(())
223    }
224}
225
226/// Implementation to easily write a `v4l2_ext_controls` struct and its array of controls to a
227/// descriptor chain.
228impl ToDescriptorChain for (v4l2_ext_controls, Vec<v4l2_ext_control>) {
229    fn write_to_chain<W: WriteToDescriptorChain>(self, writer: &mut W) -> std::io::Result<()> {
230        let (ctrls, ctrl_array) = self;
231        let mut ctrls = ctrls;
232
233        // Nullify the control pointer to avoid leaking host addresses.
234        ctrls.controls = std::ptr::null_mut();
235        writer.write_obj(ctrls)?;
236
237        for ctrl in ctrl_array {
238            writer.write_obj(ctrl)?;
239        }
240
241        Ok(())
242    }
243}
244
245/// Returns `ENOTTY` to signal that an ioctl is not handled by this device.
246macro_rules! unhandled_ioctl {
247    () => {
248        Err(libc::ENOTTY)
249    };
250}
251
252pub type IoctlResult<T> = Result<T, i32>;
253
254/// Trait for implementing ioctls supported by a device.
255///
256/// It provides a default implementation for all ioctls that returns the error code for an
257/// unsupported ioctl (`ENOTTY`) to the driver. This means that a device just needs to implement
258/// this trait and override the ioctls it supports in order to provide the expected behavior. All
259/// parsing and input validation is done by the companion function [`virtio_media_dispatch_ioctl`].
260#[allow(unused_variables)]
261pub trait VirtioMediaIoctlHandler {
262    type Session;
263
264    fn enum_fmt(
265        &mut self,
266        session: &Self::Session,
267        queue: QueueType,
268        index: u32,
269    ) -> IoctlResult<v4l2_fmtdesc> {
270        unhandled_ioctl!()
271    }
272    fn g_fmt(&mut self, session: &Self::Session, queue: QueueType) -> IoctlResult<v4l2_format> {
273        unhandled_ioctl!()
274    }
275    /// Hook for the `VIDIOC_S_FMT` ioctl.
276    ///
277    /// `queue` is guaranteed to match `format.type_`.
278    fn s_fmt(
279        &mut self,
280        session: &mut Self::Session,
281        queue: QueueType,
282        format: v4l2_format,
283    ) -> IoctlResult<v4l2_format> {
284        unhandled_ioctl!()
285    }
286    fn reqbufs(
287        &mut self,
288        session: &mut Self::Session,
289        queue: QueueType,
290        memory: MemoryType,
291        count: u32,
292        flags: MemoryConsistency,
293    ) -> IoctlResult<v4l2_requestbuffers> {
294        unhandled_ioctl!()
295    }
296    fn querybuf(
297        &mut self,
298        session: &Self::Session,
299        queue: QueueType,
300        index: u32,
301    ) -> IoctlResult<V4l2Buffer> {
302        unhandled_ioctl!()
303    }
304
305    // TODO qbuf needs a better structure to represent a buffer and its potential guest buffers.
306    fn qbuf(
307        &mut self,
308        session: &mut Self::Session,
309        buffer: V4l2Buffer,
310        guest_regions: Vec<Vec<SgEntry>>,
311    ) -> IoctlResult<V4l2Buffer> {
312        unhandled_ioctl!()
313    }
314
315    // TODO expbuf
316
317    fn streamon(&mut self, session: &mut Self::Session, queue: QueueType) -> IoctlResult<()> {
318        unhandled_ioctl!()
319    }
320    fn streamoff(&mut self, session: &mut Self::Session, queue: QueueType) -> IoctlResult<()> {
321        unhandled_ioctl!()
322    }
323
324    fn g_parm(
325        &mut self,
326        session: &Self::Session,
327        queue: QueueType,
328    ) -> IoctlResult<v4l2_streamparm> {
329        unhandled_ioctl!()
330    }
331    fn s_parm(
332        &mut self,
333        session: &mut Self::Session,
334        parm: v4l2_streamparm,
335    ) -> IoctlResult<v4l2_streamparm> {
336        unhandled_ioctl!()
337    }
338
339    fn g_std(&mut self, session: &Self::Session) -> IoctlResult<v4l2_std_id> {
340        unhandled_ioctl!()
341    }
342
343    fn s_std(&mut self, session: &mut Self::Session, std: v4l2_std_id) -> IoctlResult<()> {
344        unhandled_ioctl!()
345    }
346
347    fn enumstd(&mut self, session: &Self::Session, index: u32) -> IoctlResult<v4l2_standard> {
348        unhandled_ioctl!()
349    }
350
351    fn enuminput(&mut self, session: &Self::Session, index: u32) -> IoctlResult<v4l2_input> {
352        unhandled_ioctl!()
353    }
354
355    fn g_ctrl(&mut self, session: &Self::Session, id: u32) -> IoctlResult<v4l2_control> {
356        unhandled_ioctl!()
357    }
358
359    fn s_ctrl(
360        &mut self,
361        session: &mut Self::Session,
362        id: u32,
363        value: i32,
364    ) -> IoctlResult<v4l2_control> {
365        unhandled_ioctl!()
366    }
367
368    fn g_tuner(&mut self, session: &Self::Session, index: u32) -> IoctlResult<v4l2_tuner> {
369        unhandled_ioctl!()
370    }
371
372    fn s_tuner(
373        &mut self,
374        session: &mut Self::Session,
375        index: u32,
376        mode: TunerMode,
377    ) -> IoctlResult<()> {
378        unhandled_ioctl!()
379    }
380
381    fn g_audio(&mut self, session: &Self::Session) -> IoctlResult<v4l2_audio> {
382        unhandled_ioctl!()
383    }
384
385    fn s_audio(
386        &mut self,
387        session: &mut Self::Session,
388        index: u32,
389        mode: Option<AudioMode>,
390    ) -> IoctlResult<()> {
391        unhandled_ioctl!()
392    }
393
394    fn queryctrl(
395        &mut self,
396        session: &Self::Session,
397        id: CtrlId,
398        flags: QueryCtrlFlags,
399    ) -> IoctlResult<v4l2_queryctrl> {
400        unhandled_ioctl!()
401    }
402
403    fn querymenu(
404        &mut self,
405        session: &Self::Session,
406        id: u32,
407        index: u32,
408    ) -> IoctlResult<v4l2_querymenu> {
409        unhandled_ioctl!()
410    }
411
412    fn g_input(&mut self, session: &Self::Session) -> IoctlResult<i32> {
413        unhandled_ioctl!()
414    }
415
416    fn s_input(&mut self, session: &mut Self::Session, input: i32) -> IoctlResult<i32> {
417        unhandled_ioctl!()
418    }
419
420    fn g_output(&mut self, session: &Self::Session) -> IoctlResult<i32> {
421        unhandled_ioctl!()
422    }
423
424    fn s_output(&mut self, session: &mut Self::Session, output: i32) -> IoctlResult<i32> {
425        unhandled_ioctl!()
426    }
427
428    fn enumoutput(&mut self, session: &Self::Session, index: u32) -> IoctlResult<v4l2_output> {
429        unhandled_ioctl!()
430    }
431
432    fn g_audout(&mut self, session: &Self::Session) -> IoctlResult<v4l2_audioout> {
433        unhandled_ioctl!()
434    }
435
436    fn s_audout(&mut self, session: &mut Self::Session, index: u32) -> IoctlResult<()> {
437        unhandled_ioctl!()
438    }
439
440    fn g_modulator(&mut self, session: &Self::Session, index: u32) -> IoctlResult<v4l2_modulator> {
441        unhandled_ioctl!()
442    }
443
444    fn s_modulator(
445        &mut self,
446        session: &mut Self::Session,
447        index: u32,
448        flags: TunerTransmissionFlags,
449    ) -> IoctlResult<()> {
450        unhandled_ioctl!()
451    }
452
453    fn g_frequency(&mut self, session: &Self::Session, tuner: u32) -> IoctlResult<v4l2_frequency> {
454        unhandled_ioctl!()
455    }
456
457    fn s_frequency(
458        &mut self,
459        session: &mut Self::Session,
460        tuner: u32,
461        type_: TunerType,
462        frequency: u32,
463    ) -> IoctlResult<()> {
464        unhandled_ioctl!()
465    }
466
467    fn querystd(&mut self, session: &Self::Session) -> IoctlResult<v4l2_std_id> {
468        unhandled_ioctl!()
469    }
470
471    /// Hook for the `VIDIOC_TRY_FMT` ioctl.
472    ///
473    /// `queue` is guaranteed to match `format.type_`.
474    fn try_fmt(
475        &mut self,
476        session: &Self::Session,
477        queue: QueueType,
478        format: v4l2_format,
479    ) -> IoctlResult<v4l2_format> {
480        unhandled_ioctl!()
481    }
482
483    fn enumaudio(&mut self, session: &Self::Session, index: u32) -> IoctlResult<v4l2_audio> {
484        unhandled_ioctl!()
485    }
486
487    fn enumaudout(&mut self, session: &Self::Session, index: u32) -> IoctlResult<v4l2_audioout> {
488        unhandled_ioctl!()
489    }
490
491    /// Ext control ioctls modify `ctrls` and `ctrl_array` in place instead of returning them.
492    fn g_ext_ctrls(
493        &mut self,
494        session: &Self::Session,
495        which: CtrlWhich,
496        ctrls: &mut v4l2_ext_controls,
497        ctrl_array: &mut Vec<v4l2_ext_control>,
498        user_regions: Vec<Vec<SgEntry>>,
499    ) -> IoctlResult<()> {
500        unhandled_ioctl!()
501    }
502    /// Ext control ioctls modify `ctrls` and `ctrl_array` in place instead of returning them.
503    fn s_ext_ctrls(
504        &mut self,
505        session: &mut Self::Session,
506        which: CtrlWhich,
507        ctrls: &mut v4l2_ext_controls,
508        ctrl_array: &mut Vec<v4l2_ext_control>,
509        user_regions: Vec<Vec<SgEntry>>,
510    ) -> IoctlResult<()> {
511        unhandled_ioctl!()
512    }
513    /// Ext control ioctls modify `ctrls` and `ctrl_array` in place instead of returning them.
514    fn try_ext_ctrls(
515        &mut self,
516        session: &Self::Session,
517        which: CtrlWhich,
518        ctrls: &mut v4l2_ext_controls,
519        ctrl_array: &mut Vec<v4l2_ext_control>,
520        user_regions: Vec<Vec<SgEntry>>,
521    ) -> IoctlResult<()> {
522        unhandled_ioctl!()
523    }
524
525    fn enum_framesizes(
526        &mut self,
527        session: &Self::Session,
528        index: u32,
529        pixel_format: u32,
530    ) -> IoctlResult<v4l2_frmsizeenum> {
531        unhandled_ioctl!()
532    }
533
534    fn enum_frameintervals(
535        &mut self,
536        session: &Self::Session,
537        index: u32,
538        pixel_format: u32,
539        width: u32,
540        height: u32,
541    ) -> IoctlResult<v4l2_frmivalenum> {
542        unhandled_ioctl!()
543    }
544
545    fn g_enc_index(&mut self, session: &Self::Session) -> IoctlResult<v4l2_enc_idx> {
546        unhandled_ioctl!()
547    }
548
549    fn encoder_cmd(
550        &mut self,
551        session: &mut Self::Session,
552        cmd: v4l2_encoder_cmd,
553    ) -> IoctlResult<v4l2_encoder_cmd> {
554        unhandled_ioctl!()
555    }
556
557    fn try_encoder_cmd(
558        &mut self,
559        session: &Self::Session,
560        cmd: v4l2_encoder_cmd,
561    ) -> IoctlResult<v4l2_encoder_cmd> {
562        unhandled_ioctl!()
563    }
564
565    fn s_dv_timings(
566        &mut self,
567        session: &mut Self::Session,
568        timings: v4l2_dv_timings,
569    ) -> IoctlResult<v4l2_dv_timings> {
570        unhandled_ioctl!()
571    }
572
573    fn g_dv_timings(&mut self, session: &Self::Session) -> IoctlResult<v4l2_dv_timings> {
574        unhandled_ioctl!()
575    }
576
577    fn subscribe_event(
578        &mut self,
579        session: &mut Self::Session,
580        event: V4l2EventType,
581        flags: SubscribeEventFlags,
582    ) -> IoctlResult<()> {
583        unhandled_ioctl!()
584    }
585
586    fn unsubscribe_event(
587        &mut self,
588        session: &mut Self::Session,
589        event: v4l2_event_subscription,
590    ) -> IoctlResult<()> {
591        unhandled_ioctl!()
592    }
593
594    /// `queue` and `memory` are validated versions of the information in `create_buffers`.
595    ///
596    /// `create_buffers` is modified in place and returned to the guest event in case of error.
597    fn create_bufs(
598        &mut self,
599        session: &mut Self::Session,
600        count: u32,
601        queue: QueueType,
602        memory: MemoryType,
603        format: v4l2_format,
604    ) -> IoctlResult<v4l2_create_buffers> {
605        unhandled_ioctl!()
606    }
607
608    // TODO like qbuf, this needs a better structure to represent a buffer and its potential guest
609    // buffers.
610    fn prepare_buf(
611        &mut self,
612        session: &mut Self::Session,
613        buffer: V4l2Buffer,
614        guest_regions: Vec<Vec<SgEntry>>,
615    ) -> IoctlResult<V4l2Buffer> {
616        unhandled_ioctl!()
617    }
618
619    fn g_selection(
620        &mut self,
621        session: &Self::Session,
622        sel_type: SelectionType,
623        sel_target: SelectionTarget,
624    ) -> IoctlResult<v4l2_rect> {
625        unhandled_ioctl!()
626    }
627
628    fn s_selection(
629        &mut self,
630        session: &mut Self::Session,
631        sel_type: SelectionType,
632        sel_target: SelectionTarget,
633        sel_rect: v4l2_rect,
634        sel_flags: SelectionFlags,
635    ) -> IoctlResult<v4l2_rect> {
636        unhandled_ioctl!()
637    }
638
639    fn decoder_cmd(
640        &mut self,
641        session: &mut Self::Session,
642        cmd: v4l2_decoder_cmd,
643    ) -> IoctlResult<v4l2_decoder_cmd> {
644        unhandled_ioctl!()
645    }
646
647    fn try_decoder_cmd(
648        &mut self,
649        session: &Self::Session,
650        cmd: v4l2_decoder_cmd,
651    ) -> IoctlResult<v4l2_decoder_cmd> {
652        unhandled_ioctl!()
653    }
654
655    fn enum_dv_timings(
656        &mut self,
657        session: &Self::Session,
658        index: u32,
659    ) -> IoctlResult<v4l2_dv_timings> {
660        unhandled_ioctl!()
661    }
662
663    fn query_dv_timings(&mut self, session: &Self::Session) -> IoctlResult<v4l2_dv_timings> {
664        unhandled_ioctl!()
665    }
666
667    fn dv_timings_cap(&self, session: &Self::Session) -> IoctlResult<v4l2_dv_timings_cap> {
668        unhandled_ioctl!()
669    }
670
671    fn enum_freq_bands(
672        &self,
673        session: &Self::Session,
674        tuner: u32,
675        type_: TunerType,
676        index: u32,
677    ) -> IoctlResult<v4l2_frequency_band> {
678        unhandled_ioctl!()
679    }
680
681    fn query_ext_ctrl(
682        &mut self,
683        session: &Self::Session,
684        id: CtrlId,
685        flags: QueryCtrlFlags,
686    ) -> IoctlResult<v4l2_query_ext_ctrl> {
687        unhandled_ioctl!()
688    }
689}
690
691/// Writes a `ENOTTY` error response into `writer` to signal that an ioctl is not implemented by
692/// the device.
693fn invalid_ioctl<W: WriteToDescriptorChain>(code: V4l2Ioctl, writer: &mut W) -> IoResult<()> {
694    writer.write_err_response(libc::ENOTTY).map_err(|e| {
695        log::error!(
696            "failed to write error response for invalid ioctl {:?}: {:#}",
697            code,
698            e
699        );
700        e
701    })
702}
703
704/// Implements a `WR` ioctl for which errors may also carry a payload.
705///
706/// * `Reader` is the reader to the device-readable part of the descriptor chain,
707/// * `Writer` is the writer to the device-writable part of the descriptor chain,
708/// * `I` is the data to be read from the descriptor chain,
709/// * `O` is the type of response to be written to the descriptor chain for both success and
710///   failure,
711/// * `X` processes the input and produces a result. In case of failure, an error code and optional
712///   payload to write along with it are returned.
713fn wr_ioctl_with_err_payload<Reader, Writer, I, O, X>(
714    ioctl: V4l2Ioctl,
715    reader: &mut Reader,
716    writer: &mut Writer,
717    process: X,
718) -> IoResult<()>
719where
720    Reader: ReadFromDescriptorChain,
721    Writer: WriteToDescriptorChain,
722    I: FromDescriptorChain,
723    O: ToDescriptorChain,
724    X: FnOnce(I) -> Result<O, (i32, Option<O>)>,
725{
726    let input = match I::read_from_chain(reader) {
727        Ok(input) => input,
728        Err(e) => {
729            log::error!("error while reading input for {:?} ioctl: {:#}", ioctl, e);
730            return writer.write_err_response(libc::EINVAL);
731        }
732    };
733
734    let (resp_header, output) = match process(input) {
735        Ok(output) => (RespHeader::ok(), Some(output)),
736        Err((errno, output)) => (RespHeader::err(errno), output),
737    };
738
739    writer.write_response(resp_header)?;
740    if let Some(output) = output {
741        output.write_to_chain(writer)?;
742    }
743
744    Ok(())
745}
746
747/// Implements a `WR` ioctl for which errors do not carry a payload.
748///
749/// * `Reader` is the reader to the device-readable part of the descriptor chain,
750/// * `Writer` is the writer to the device-writable part of the descriptor chain,
751/// * `I` is the data to be read from the descriptor chain,
752/// * `O` is the type of response to be written to the descriptor chain in case of success,
753/// * `X` processes the input and produces a result. In case of failure, an error code to transmit
754///   to the guest is returned.
755fn wr_ioctl<Reader, Writer, I, O, X>(
756    ioctl: V4l2Ioctl,
757    reader: &mut Reader,
758    writer: &mut Writer,
759    process: X,
760) -> IoResult<()>
761where
762    Reader: ReadFromDescriptorChain,
763    Writer: WriteToDescriptorChain,
764    I: FromDescriptorChain,
765    O: ToDescriptorChain,
766    X: FnOnce(I) -> Result<O, i32>,
767{
768    wr_ioctl_with_err_payload(ioctl, reader, writer, |input| {
769        process(input).map_err(|err| (err, None))
770    })
771}
772
773/// Implements a `W` ioctl.
774///
775/// * `Reader` is the reader to the device-readable part of the descriptor chain,
776/// * `I` is the data to be read from the descriptor chain,
777/// * `X` processes the input. In case of failure, an error code to transmit to the guest is
778///   returned.
779fn w_ioctl<Reader, Writer, I, X>(
780    ioctl: V4l2Ioctl,
781    reader: &mut Reader,
782    writer: &mut Writer,
783    process: X,
784) -> IoResult<()>
785where
786    I: FromDescriptorChain,
787    Reader: ReadFromDescriptorChain,
788    Writer: WriteToDescriptorChain,
789    X: FnOnce(I) -> Result<(), i32>,
790{
791    wr_ioctl(ioctl, reader, writer, process)
792}
793
794/// Implements a `R` ioctl.
795///
796/// * `Writer` is the writer to the device-writable part of the descriptor chain,
797/// * `O` is the type of response to be written to the descriptor chain in case of success,
798/// * `X` runs the ioctl and produces a result. In case of failure, an error code to transmit to
799///   the guest is returned.
800fn r_ioctl<Writer, O, X>(ioctl: V4l2Ioctl, writer: &mut Writer, process: X) -> IoResult<()>
801where
802    Writer: WriteToDescriptorChain,
803    O: ToDescriptorChain,
804    X: FnOnce() -> Result<O, i32>,
805{
806    wr_ioctl(ioctl, &mut std::io::empty(), writer, |()| process())
807}
808
809/// Ensures that the `readbuffers` and `writebuffers` members of a `v4l2_streamparm` are zero since
810/// we do not expose the `READWRITE` capability.
811fn patch_streamparm(mut parm: v4l2_streamparm) -> v4l2_streamparm {
812    match QueueType::n(parm.type_)
813        .unwrap_or(QueueType::VideoCapture)
814        .direction()
815    {
816        QueueDirection::Output => parm.parm.output.writebuffers = 0,
817        QueueDirection::Capture => parm.parm.capture.readbuffers = 0,
818    }
819
820    parm
821}
822
823/// IOCTL dispatcher for implementors of [`VirtioMediaIoctlHandler`].
824///
825/// This function takes care of reading and validating IOCTL inputs and writing outputs or errors
826/// back to the driver, invoking the relevant method of the handler in the middle.
827///
828/// Implementors of [`VirtioMediaIoctlHandler`] can thus just focus on writing the desired behavior
829/// for their device, and let the more tedious parsing and validation to this function.
830pub fn virtio_media_dispatch_ioctl<S, H, Reader, Writer>(
831    handler: &mut H,
832    session: &mut S,
833    ioctl: V4l2Ioctl,
834    reader: &mut Reader,
835    writer: &mut Writer,
836) -> IoResult<()>
837where
838    H: VirtioMediaIoctlHandler<Session = S>,
839    Reader: ReadFromDescriptorChain,
840    Writer: WriteToDescriptorChain,
841{
842    use V4l2Ioctl::*;
843
844    match ioctl {
845        VIDIOC_QUERYCAP => invalid_ioctl(ioctl, writer),
846        VIDIOC_ENUM_FMT => wr_ioctl(ioctl, reader, writer, |format: v4l2_fmtdesc| {
847            let queue = QueueType::n(format.type_).ok_or(libc::EINVAL)?;
848            handler.enum_fmt(session, queue, format.index)
849        }),
850        VIDIOC_G_FMT => wr_ioctl(ioctl, reader, writer, |format: v4l2_format| {
851            let queue = QueueType::n(format.type_).ok_or(libc::EINVAL)?;
852            handler.g_fmt(session, queue)
853        }),
854        VIDIOC_S_FMT => wr_ioctl(ioctl, reader, writer, |format: v4l2_format| {
855            let queue = QueueType::n(format.type_).ok_or(libc::EINVAL)?;
856            handler.s_fmt(session, queue, format)
857        }),
858        VIDIOC_REQBUFS => wr_ioctl(ioctl, reader, writer, |reqbufs: v4l2_requestbuffers| {
859            let queue = QueueType::n(reqbufs.type_).ok_or(libc::EINVAL)?;
860            let memory = MemoryType::n(reqbufs.memory).ok_or(libc::EINVAL)?;
861            let flags = MemoryConsistency::from_bits(reqbufs.flags).ok_or(libc::EINVAL)?;
862
863            match memory {
864                MemoryType::Mmap | MemoryType::UserPtr => (),
865                t => {
866                    log::error!(
867                        "VIDIOC_REQBUFS: memory type {:?} is currently unsupported",
868                        t
869                    );
870                    return Err(libc::EINVAL);
871                }
872            }
873
874            handler.reqbufs(session, queue, memory, reqbufs.count, flags)
875        }),
876        VIDIOC_QUERYBUF => {
877            wr_ioctl(ioctl, reader, writer, |buffer: v4l2_buffer| {
878                let queue = QueueType::n(buffer.type_).ok_or(libc::EINVAL)?;
879                // Maximum number of planes we can write back to the driver.
880                let num_planes = if queue.is_multiplanar() {
881                    buffer.length as usize
882                } else {
883                    0
884                };
885
886                handler
887                    .querybuf(session, queue, buffer.index)
888                    .map(|guest_buffer| (guest_buffer, num_planes))
889            })
890        }
891        VIDIOC_G_FBUF => invalid_ioctl(ioctl, writer),
892        VIDIOC_S_FBUF => invalid_ioctl(ioctl, writer),
893        VIDIOC_OVERLAY => invalid_ioctl(ioctl, writer),
894        VIDIOC_QBUF => wr_ioctl(ioctl, reader, writer, |(guest_buffer, guest_regions)| {
895            let num_planes = guest_buffer.num_planes();
896
897            handler
898                .qbuf(session, guest_buffer, guest_regions)
899                .map(|guest_buffer| (guest_buffer, num_planes))
900        }),
901        // TODO implement EXPBUF.
902        VIDIOC_EXPBUF => invalid_ioctl(ioctl, writer),
903        VIDIOC_DQBUF => invalid_ioctl(ioctl, writer),
904        VIDIOC_STREAMON => w_ioctl(ioctl, reader, writer, |input: u32| {
905            let queue = QueueType::n(input).ok_or(libc::EINVAL)?;
906
907            handler.streamon(session, queue)
908        }),
909        VIDIOC_STREAMOFF => w_ioctl(ioctl, reader, writer, |input: u32| {
910            let queue = QueueType::n(input).ok_or(libc::EINVAL)?;
911
912            handler.streamoff(session, queue)
913        }),
914        VIDIOC_G_PARM => wr_ioctl(ioctl, reader, writer, |parm: v4l2_streamparm| {
915            let queue = QueueType::n(parm.type_).ok_or(libc::EINVAL)?;
916
917            handler.g_parm(session, queue).map(patch_streamparm)
918        }),
919        VIDIOC_S_PARM => wr_ioctl(ioctl, reader, writer, |parm: v4l2_streamparm| {
920            handler
921                .s_parm(session, patch_streamparm(parm))
922                .map(patch_streamparm)
923        }),
924        VIDIOC_G_STD => r_ioctl(ioctl, writer, || handler.g_std(session)),
925        VIDIOC_S_STD => w_ioctl(ioctl, reader, writer, |id: v4l2_std_id| {
926            handler.s_std(session, id)
927        }),
928        VIDIOC_ENUMSTD => wr_ioctl(ioctl, reader, writer, |std: v4l2_standard| {
929            handler.enumstd(session, std.index)
930        }),
931        VIDIOC_ENUMINPUT => wr_ioctl(ioctl, reader, writer, |input: v4l2_input| {
932            handler.enuminput(session, input.index)
933        }),
934        VIDIOC_G_CTRL => wr_ioctl(ioctl, reader, writer, |ctrl: v4l2_control| {
935            handler.g_ctrl(session, ctrl.id)
936        }),
937        VIDIOC_S_CTRL => wr_ioctl(ioctl, reader, writer, |ctrl: v4l2_control| {
938            handler.s_ctrl(session, ctrl.id, ctrl.value)
939        }),
940        VIDIOC_G_TUNER => wr_ioctl(ioctl, reader, writer, |tuner: v4l2_tuner| {
941            handler.g_tuner(session, tuner.index)
942        }),
943        VIDIOC_S_TUNER => w_ioctl(ioctl, reader, writer, |tuner: v4l2_tuner| {
944            let mode = TunerMode::n(tuner.audmode).ok_or(libc::EINVAL)?;
945            handler.s_tuner(session, tuner.index, mode)
946        }),
947        VIDIOC_G_AUDIO => r_ioctl(ioctl, writer, || handler.g_audio(session)),
948        VIDIOC_S_AUDIO => w_ioctl(ioctl, reader, writer, |input: v4l2_audio| {
949            handler.s_audio(session, input.index, AudioMode::n(input.mode))
950        }),
951        VIDIOC_QUERYCTRL => wr_ioctl(ioctl, reader, writer, |input: v4l2_queryctrl| {
952            let (id, flags) = v4l2r::ioctl::parse_ctrl_id_and_flags(input.id);
953
954            handler.queryctrl(session, id, flags)
955        }),
956        VIDIOC_QUERYMENU => wr_ioctl(ioctl, reader, writer, |input: v4l2_querymenu| {
957            handler.querymenu(session, input.id, input.index)
958        }),
959        VIDIOC_G_INPUT => r_ioctl(ioctl, writer, || handler.g_input(session)),
960        VIDIOC_S_INPUT => wr_ioctl(ioctl, reader, writer, |input: i32| {
961            handler.s_input(session, input)
962        }),
963        VIDIOC_G_EDID => invalid_ioctl(ioctl, writer),
964        VIDIOC_S_EDID => invalid_ioctl(ioctl, writer),
965        VIDIOC_G_OUTPUT => r_ioctl(ioctl, writer, || handler.g_output(session)),
966        VIDIOC_S_OUTPUT => wr_ioctl(ioctl, reader, writer, |output: i32| {
967            handler.s_output(session, output)
968        }),
969        VIDIOC_ENUMOUTPUT => wr_ioctl(ioctl, reader, writer, |output: v4l2_output| {
970            handler.enumoutput(session, output.index)
971        }),
972        VIDIOC_G_AUDOUT => r_ioctl(ioctl, writer, || handler.g_audout(session)),
973        VIDIOC_S_AUDOUT => w_ioctl(ioctl, reader, writer, |audout: v4l2_audioout| {
974            handler.s_audout(session, audout.index)
975        }),
976        VIDIOC_G_MODULATOR => wr_ioctl(ioctl, reader, writer, |modulator: v4l2_modulator| {
977            handler.g_modulator(session, modulator.index)
978        }),
979        VIDIOC_S_MODULATOR => w_ioctl(ioctl, reader, writer, |modulator: v4l2_modulator| {
980            let flags =
981                TunerTransmissionFlags::from_bits(modulator.txsubchans).ok_or(libc::EINVAL)?;
982            handler.s_modulator(session, modulator.index, flags)
983        }),
984        VIDIOC_G_FREQUENCY => wr_ioctl(ioctl, reader, writer, |freq: v4l2_frequency| {
985            handler.g_frequency(session, freq.tuner)
986        }),
987        VIDIOC_S_FREQUENCY => w_ioctl(ioctl, reader, writer, |freq: v4l2_frequency| {
988            let type_ = TunerType::n(freq.type_).ok_or(libc::EINVAL)?;
989
990            handler.s_frequency(session, freq.tuner, type_, freq.frequency)
991        }),
992        // TODO do these 3 need to be supported?
993        VIDIOC_CROPCAP => invalid_ioctl(ioctl, writer),
994        VIDIOC_G_CROP => invalid_ioctl(ioctl, writer),
995        VIDIOC_S_CROP => invalid_ioctl(ioctl, writer),
996        // Deprecated in V4L2.
997        VIDIOC_G_JPEGCOMP => invalid_ioctl(ioctl, writer),
998        // Deprecated in V4L2.
999        VIDIOC_S_JPEGCOMP => invalid_ioctl(ioctl, writer),
1000        VIDIOC_QUERYSTD => r_ioctl(ioctl, writer, || handler.querystd(session)),
1001        VIDIOC_TRY_FMT => wr_ioctl(ioctl, reader, writer, |format: v4l2_format| {
1002            let queue = QueueType::n(format.type_).ok_or(libc::EINVAL)?;
1003            handler.try_fmt(session, queue, format)
1004        }),
1005        VIDIOC_ENUMAUDIO => wr_ioctl(ioctl, reader, writer, |audio: v4l2_audio| {
1006            handler.enumaudio(session, audio.index)
1007        }),
1008        VIDIOC_ENUMAUDOUT => wr_ioctl(ioctl, reader, writer, |audio: v4l2_audioout| {
1009            handler.enumaudout(session, audio.index)
1010        }),
1011        VIDIOC_G_PRIORITY => invalid_ioctl(ioctl, writer),
1012        VIDIOC_S_PRIORITY => invalid_ioctl(ioctl, writer),
1013        // TODO support this, although it's marginal.
1014        VIDIOC_G_SLICED_VBI_CAP => invalid_ioctl(ioctl, writer),
1015        // Doesn't make sense in a virtual context.
1016        VIDIOC_LOG_STATUS => invalid_ioctl(ioctl, writer),
1017        VIDIOC_G_EXT_CTRLS => wr_ioctl_with_err_payload(
1018            ioctl,
1019            reader,
1020            writer,
1021            |(mut ctrls, mut ctrl_array, user_regions)| {
1022                let which = CtrlWhich::try_from(&ctrls).map_err(|()| (libc::EINVAL, None))?;
1023
1024                match handler.g_ext_ctrls(session, which, &mut ctrls, &mut ctrl_array, user_regions)
1025                {
1026                    Ok(()) => Ok((ctrls, ctrl_array)),
1027                    // It is very important what we write back the updated input in case
1028                    // of error as it contains extra information.
1029                    Err(e) => Err((e, Some((ctrls, ctrl_array)))),
1030                }
1031            },
1032        ),
1033        VIDIOC_S_EXT_CTRLS => wr_ioctl_with_err_payload(
1034            ioctl,
1035            reader,
1036            writer,
1037            |(mut ctrls, mut ctrl_array, user_regions)| {
1038                let which = CtrlWhich::try_from(&ctrls).map_err(|()| (libc::EINVAL, None))?;
1039
1040                match handler.s_ext_ctrls(session, which, &mut ctrls, &mut ctrl_array, user_regions)
1041                {
1042                    Ok(()) => Ok((ctrls, ctrl_array)),
1043                    // It is very important what we write back the updated input in case
1044                    // of error as it contains extra information.
1045                    Err(e) => Err((e, Some((ctrls, ctrl_array)))),
1046                }
1047            },
1048        ),
1049        VIDIOC_TRY_EXT_CTRLS => wr_ioctl_with_err_payload(
1050            ioctl,
1051            reader,
1052            writer,
1053            |(mut ctrls, mut ctrl_array, user_regions)| {
1054                let which = CtrlWhich::try_from(&ctrls).map_err(|()| (libc::EINVAL, None))?;
1055
1056                match handler.try_ext_ctrls(
1057                    session,
1058                    which,
1059                    &mut ctrls,
1060                    &mut ctrl_array,
1061                    user_regions,
1062                ) {
1063                    Ok(()) => Ok((ctrls, ctrl_array)),
1064                    // It is very important what we write back the updated input in case
1065                    // of error as it contains extra information.
1066                    Err(e) => Err((e, Some((ctrls, ctrl_array)))),
1067                }
1068            },
1069        ),
1070        VIDIOC_ENUM_FRAMESIZES => {
1071            wr_ioctl(ioctl, reader, writer, |frmsizeenum: v4l2_frmsizeenum| {
1072                handler.enum_framesizes(session, frmsizeenum.index, frmsizeenum.pixel_format)
1073            })
1074        }
1075        VIDIOC_ENUM_FRAMEINTERVALS => {
1076            wr_ioctl(ioctl, reader, writer, |frmivalenum: v4l2_frmivalenum| {
1077                handler.enum_frameintervals(
1078                    session,
1079                    frmivalenum.index,
1080                    frmivalenum.pixel_format,
1081                    frmivalenum.width,
1082                    frmivalenum.height,
1083                )
1084            })
1085        }
1086        VIDIOC_G_ENC_INDEX => r_ioctl(ioctl, writer, || handler.g_enc_index(session)),
1087        VIDIOC_ENCODER_CMD => wr_ioctl(ioctl, reader, writer, |cmd: v4l2_encoder_cmd| {
1088            handler.encoder_cmd(session, cmd)
1089        }),
1090        VIDIOC_TRY_ENCODER_CMD => wr_ioctl(ioctl, reader, writer, |cmd: v4l2_encoder_cmd| {
1091            handler.try_encoder_cmd(session, cmd)
1092        }),
1093        // Doesn't make sense in a virtual context.
1094        VIDIOC_DBG_G_REGISTER => invalid_ioctl(ioctl, writer),
1095        // Doesn't make sense in a virtual context.
1096        VIDIOC_DBG_S_REGISTER => invalid_ioctl(ioctl, writer),
1097        VIDIOC_S_HW_FREQ_SEEK => invalid_ioctl(ioctl, writer),
1098        VIDIOC_S_DV_TIMINGS => wr_ioctl(ioctl, reader, writer, |timings: v4l2_dv_timings| {
1099            handler.s_dv_timings(session, timings)
1100        }),
1101        VIDIOC_G_DV_TIMINGS => wr_ioctl(
1102            ioctl,
1103            reader,
1104            writer,
1105            // We are not using the input - this should probably have been a R ioctl?
1106            |_: v4l2_dv_timings| handler.g_dv_timings(session),
1107        ),
1108        // Supported by an event.
1109        VIDIOC_DQEVENT => invalid_ioctl(ioctl, writer),
1110        VIDIOC_SUBSCRIBE_EVENT => {
1111            w_ioctl(ioctl, reader, writer, |input: v4l2_event_subscription| {
1112                let event = V4l2EventType::try_from(&input).unwrap();
1113                let flags = SubscribeEventFlags::from_bits(input.flags).unwrap();
1114
1115                handler.subscribe_event(session, event, flags)
1116            })?;
1117
1118            Ok(())
1119        }
1120        VIDIOC_UNSUBSCRIBE_EVENT => {
1121            w_ioctl(ioctl, reader, writer, |event: v4l2_event_subscription| {
1122                handler.unsubscribe_event(session, event)
1123            })
1124        }
1125        VIDIOC_CREATE_BUFS => wr_ioctl(ioctl, reader, writer, |input: v4l2_create_buffers| {
1126            let queue = QueueType::n(input.format.type_).ok_or(libc::EINVAL)?;
1127            let memory = MemoryType::n(input.memory).ok_or(libc::EINVAL)?;
1128
1129            handler.create_bufs(session, input.count, queue, memory, input.format)
1130        }),
1131        VIDIOC_PREPARE_BUF => wr_ioctl(ioctl, reader, writer, |(guest_buffer, guest_regions)| {
1132            let num_planes = guest_buffer.num_planes();
1133
1134            handler
1135                .prepare_buf(session, guest_buffer, guest_regions)
1136                .map(|out_buffer| (out_buffer, num_planes))
1137        }),
1138        VIDIOC_G_SELECTION => wr_ioctl(ioctl, reader, writer, |mut selection: v4l2_selection| {
1139            let sel_type = SelectionType::n(selection.type_).ok_or(libc::EINVAL)?;
1140            let sel_target = SelectionTarget::n(selection.target).ok_or(libc::EINVAL)?;
1141
1142            handler
1143                .g_selection(session, sel_type, sel_target)
1144                .map(|rect| {
1145                    selection.r = rect;
1146                    selection
1147                })
1148        }),
1149        VIDIOC_S_SELECTION => wr_ioctl(ioctl, reader, writer, |mut selection: v4l2_selection| {
1150            let sel_type = SelectionType::n(selection.type_).ok_or(libc::EINVAL)?;
1151            let sel_target = SelectionTarget::n(selection.target).ok_or(libc::EINVAL)?;
1152            let sel_flags = SelectionFlags::from_bits(selection.flags).ok_or(libc::EINVAL)?;
1153
1154            handler
1155                .s_selection(session, sel_type, sel_target, selection.r, sel_flags)
1156                .map(|rect| {
1157                    selection.r = rect;
1158                    selection
1159                })
1160        }),
1161        VIDIOC_DECODER_CMD => wr_ioctl(ioctl, reader, writer, |cmd: v4l2_decoder_cmd| {
1162            handler.decoder_cmd(session, cmd)
1163        }),
1164        VIDIOC_TRY_DECODER_CMD => wr_ioctl(ioctl, reader, writer, |cmd: v4l2_decoder_cmd| {
1165            handler.try_decoder_cmd(session, cmd)
1166        }),
1167        VIDIOC_ENUM_DV_TIMINGS => wr_ioctl(
1168            ioctl,
1169            reader,
1170            writer,
1171            |mut enum_timings: v4l2_enum_dv_timings| {
1172                handler
1173                    .enum_dv_timings(session, enum_timings.index)
1174                    .map(|timings| {
1175                        enum_timings.timings = timings;
1176                        enum_timings
1177                    })
1178            },
1179        ),
1180        VIDIOC_QUERY_DV_TIMINGS => r_ioctl(ioctl, writer, || handler.query_dv_timings(session)),
1181        VIDIOC_DV_TIMINGS_CAP => wr_ioctl(ioctl, reader, writer, |_: v4l2_dv_timings_cap| {
1182            handler.dv_timings_cap(session)
1183        }),
1184        VIDIOC_ENUM_FREQ_BANDS => {
1185            wr_ioctl(ioctl, reader, writer, |freq_band: v4l2_frequency_band| {
1186                let type_ = TunerType::n(freq_band.type_).ok_or(libc::EINVAL)?;
1187
1188                handler.enum_freq_bands(session, freq_band.tuner, type_, freq_band.index)
1189            })
1190        }
1191        // Doesn't make sense in a virtual context.
1192        VIDIOC_DBG_G_CHIP_INFO => invalid_ioctl(ioctl, writer),
1193        VIDIOC_QUERY_EXT_CTRL => wr_ioctl(ioctl, reader, writer, |ctrl: v4l2_query_ext_ctrl| {
1194            let (id, flags) = v4l2r::ioctl::parse_ctrl_id_and_flags(ctrl.id);
1195            handler.query_ext_ctrl(session, id, flags)
1196        }),
1197    }
1198}