• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2025 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 use std::ffi::c_char;
6 use std::path::PathBuf;
7 use std::sync::Arc;
8 
9 use v4l2r::device::queue::Queue;
10 use v4l2r::device::{Device as VideoDevice, DeviceConfig};
11 use v4l2r::ioctl::Capability;
12 use v4l2r::nix::fcntl::{open, OFlag};
13 use v4l2r::nix::sys::stat::Mode;
14 use zerocopy::FromZeros;
15 
16 const MAX_DEVICE_NO: usize = 128;
17 
18 /// Enumerate V4L2 (video and media) devices on the system.
enumerate_devices() -> Option<(PathBuf, PathBuf)>19 pub fn enumerate_devices() -> Option<(PathBuf, PathBuf)> {
20     let decoder_device_prefix = "/dev/video";
21 
22     for dev_no in 0..MAX_DEVICE_NO {
23         let video_device_path = PathBuf::from(format!("{}{}", decoder_device_prefix, dev_no));
24         let video_device_config = DeviceConfig::new().non_blocking_dqbuf();
25 
26         let device = match VideoDevice::open(&video_device_path, video_device_config) {
27             Ok(device) => Arc::new(device),
28             Err(_) => continue,
29         };
30 
31         if Queue::get_output_mplane_queue(device.clone()).is_err() {
32             continue;
33         }
34 
35         let caps = device.caps();
36         if let Some(media_device_path) = find_media_device(caps) {
37             log::info!(
38                 "Using video device {:?} with media device {:?}",
39                 video_device_path,
40                 media_device_path
41             );
42             return Some((video_device_path, media_device_path));
43         }
44     }
45     None
46 }
47 
48 /// A struct that contains information about media device.
49 ///
50 /// See: https://docs.kernel.org/userspace-api/media/mediactl/media-ioc-device-info.html
51 #[repr(C)]
52 #[derive(FromZeros)]
53 pub struct MediaDeviceInfo {
54     driver: [c_char; 16],
55     model: [c_char; 32],
56     serial: [c_char; 40],
57     bus_info: [c_char; 32],
58     media_version: u32,
59     hw_revision: u32,
60     driver_version: u32,
61     reserved: [u32; 31],
62 }
63 
64 const IOCTL_MEDIA_COMMAND: u8 = b'|';
65 pub const MEDIA_IOC_DEVICE_INFO: u8 = 0x00;
66 use nix::ioctl_readwrite;
67 ioctl_readwrite!(
68     media_ioc_device_info,
69     IOCTL_MEDIA_COMMAND,
70     MEDIA_IOC_DEVICE_INFO,
71     MediaDeviceInfo
72 );
73 
parse_c_str_from_array(arr: &[c_char]) -> String74 fn parse_c_str_from_array(arr: &[c_char]) -> String {
75     let bytes: Vec<u8> = arr.iter().map(|&c| c as u8).take_while(|&c| c != 0).collect();
76     String::from_utf8_lossy(&bytes).trim().to_string()
77 }
78 
79 /// Find media device according to the decode device's capabilities.
find_media_device(cap: &Capability) -> Option<PathBuf>80 pub fn find_media_device(cap: &Capability) -> Option<PathBuf> {
81     let media_device_prefix = "/dev/media";
82 
83     for dev_no in 0..MAX_DEVICE_NO {
84         let media_device_path = PathBuf::from(format!("{}{}", media_device_prefix, dev_no));
85         let media_device =
86             match open(&media_device_path, OFlag::O_RDWR | OFlag::O_CLOEXEC, Mode::empty()) {
87                 Ok(media_device) => media_device,
88                 Err(_) => continue,
89             };
90 
91         // MediaDeviceInfo is a struct which contains only fields for which 0 is a valid
92         // bit pattern.
93         let mut media_device_info = MediaDeviceInfo::new_zeroed();
94         let media_device_info = unsafe {
95             // SAFETY: This should be safe, as the `media_ioc_device_info` ioctl is called with
96             //  `media_device` - a valid file descriptor  and `media_device_info` - a valid pointer
97             //  to `MediaDeviceInfo` struct.
98             match media_ioc_device_info(media_device, &mut media_device_info) {
99                 Ok(_) => Some(media_device_info),
100                 Err(_) => None,
101             }
102         };
103 
104         // Match the video device and the media controller by the |bus_info|
105         // field. This works better than |driver| field if there are multiple
106         // instances of the same decoder driver in the system
107         if let Some(media_bus_info) =
108             media_device_info.as_ref().map(|info| parse_c_str_from_array(&info.bus_info))
109         {
110             if !cap.bus_info.is_empty()
111                 && !media_bus_info.is_empty()
112                 && cap.bus_info == *media_bus_info
113             {
114                 return Some(media_device_path);
115             }
116         }
117 
118         // Fall back to matching the video device and the media controller by the
119         // driver field. The mtk-vcodec driver does not fill the card and bus fields
120         // properly, so those won't work.
121         if let Some(driver) =
122             media_device_info.as_ref().map(|info| parse_c_str_from_array(&info.driver))
123         {
124             if !cap.bus_info.is_empty() && !driver.is_empty() && cap.driver == *driver {
125                 return Some(media_device_path);
126             }
127         }
128     }
129     None
130 }
131