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