[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20250818-v4l2-v1-6-6887e772aac2@collabora.com>
Date: Mon, 18 Aug 2025 02:49:52 -0300
From: Daniel Almeida <daniel.almeida@...labora.com>
To: Miguel Ojeda <ojeda@...nel.org>, Alex Gaynor <alex.gaynor@...il.com>,
Boqun Feng <boqun.feng@...il.com>, Gary Guo <gary@...yguo.net>,
Björn Roy Baron <bjorn3_gh@...tonmail.com>,
Benno Lossin <lossin@...nel.org>, Andreas Hindborg <a.hindborg@...nel.org>,
Alice Ryhl <aliceryhl@...gle.com>, Trevor Gross <tmgross@...ch.edu>,
Danilo Krummrich <dakr@...nel.org>, Alexandre Courbot <acourbot@...dia.com>
Cc: linux-kernel@...r.kernel.org, rust-for-linux@...r.kernel.org,
kernel@...labora.com, linux-media@...r.kernel.org,
Daniel Almeida <daniel.almeida@...labora.com>
Subject: [PATCH 6/7] rust: v4l2: add basic ioctl support
Most of the v4l2 API is implemented through ioctl(), so adding support
for them is essential in order to be able to write v4l2 drivers.
Hook up ioctl support by filling up an instance of v4l2_ioctl_ops. For
now, we only support the most basic v4l2 ioctl: VIDIOC_QUERYCAPS. This
is used by userspace to retrieve information about the device, and is
considered enough to implement a simple v4l2 sample Rust driver.
Signed-off-by: Daniel Almeida <daniel.almeida@...labora.com>
---
rust/kernel/media/v4l2/file.rs | 1 -
rust/kernel/media/v4l2/ioctl.rs | 92 +++++++++++++++++++++++++++++++++++++++++
rust/kernel/media/v4l2/mod.rs | 3 ++
rust/kernel/media/v4l2/video.rs | 13 +++++-
4 files changed, 107 insertions(+), 2 deletions(-)
diff --git a/rust/kernel/media/v4l2/file.rs b/rust/kernel/media/v4l2/file.rs
index 37b34f8e6f251fafde5f7e6b4bd654519d8247a5..8817051268323866f41fd56a0c7e8fa4b7537858 100644
--- a/rust/kernel/media/v4l2/file.rs
+++ b/rust/kernel/media/v4l2/file.rs
@@ -50,7 +50,6 @@ impl<T: DriverFile> File<T> {
///
/// - `ptr` must be a valid pointer to a `struct v4l2_file`.
/// - `ptr` must be valid for 'a.
- #[expect(dead_code)]
pub(super) unsafe fn from_raw<'a>(ptr: *mut bindings::v4l2_fh) -> &'a File<T> {
// SAFETY: `ptr` is a valid pointer to a `struct v4l2_file` as per the
// safety requirements of this function.
diff --git a/rust/kernel/media/v4l2/ioctl.rs b/rust/kernel/media/v4l2/ioctl.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e8d20d4cb70f5722c0109ea5bad36041355fc7a1
--- /dev/null
+++ b/rust/kernel/media/v4l2/ioctl.rs
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-copyrightText: Copyright (C) 2025 Collabora Ltd.
+
+//! V4L2 device node ioctl support.
+//!
+//! Most of the V4L2 API is implemented through the ioctl system call. This
+//! module provides support for ioctl operations on V4L2 device nodes.
+
+use core::{marker::PhantomData, mem::MaybeUninit};
+
+use crate::{
+ media::v4l2::{
+ self,
+ video::{self, Driver},
+ },
+ prelude::*,
+};
+
+/// The vtable for the ioctls of a registered Rust [`video::Device`].
+///
+/// # Invariants
+///
+/// - All the callbacks in [`IoctlVtable`] are called after the underlying
+/// [`video::Device`] has been registered.
+pub(super) struct IoctlVtable<T: Driver>(PhantomData<T>);
+
+impl<T: Driver> IoctlVtable<T> {
+ /// # Safety
+ ///
+ /// This should only be called from the ioctl callbacks and the returned
+ /// reference should not outlive the callback itself.
+ unsafe fn data<'a>(file: *mut bindings::file) -> &'a <T as Driver>::Data
+ where
+ T: 'a,
+ {
+ // SAFETY: This was set during the video device registration process.
+ let vdev = unsafe { bindings::video_devdata(file) };
+
+ // SAFETY: `video_device` is a valid pointer to a `struct video_device`
+ // returned by `bindings::video_devdata` and it is valid while the
+ // reference is alive.
+ unsafe { video::Device::<T>::from_raw(vdev) }
+ }
+
+ /// # Safety
+ ///
+ /// This should only be used as the `videoc_querycap` callback.
+ unsafe extern "C" fn vidioc_querycap_callback(
+ file: *mut bindings::file,
+ _fh: *mut c_void,
+ cap: *mut bindings::v4l2_capability,
+ ) -> core::ffi::c_int {
+ // SAFETY: this is being called from an ioctl callback and the returned
+ // reference does not outlive it.
+ let data = unsafe { Self::data(file) };
+
+ // SAFETY: the fact that this is being called from an ioctl callback means that:
+ //
+ // - the video device has been registered.
+ // - `open()` has been called (as you cannot call ioctl() on a file that
+ // has not been previously opened).
+ // - as a result from the statement above, a valid `v4l2_fh` was
+ // installed in `bindings::file::private_data`, which we then convert
+ // into `File<T>` here.
+ // - `ptr` is valid for 'a, i.e.: for the scope of this function.
+ let file = unsafe { v4l2::file::File::<T::File>::from_raw((*file).private_data.cast()) };
+
+ // SAFETY: the safety requirements ensure that `cap` is a valid pointer
+ // to a `struct v4l2_capability`, which fulfills the requirements of the
+ // `Capabilities::from_raw`.
+ let cap = unsafe { v4l2::caps::Capabilities::from_raw(cap) };
+
+ match T::querycap(file, data, cap) {
+ Ok(()) => 0,
+ Err(err) => err.to_errno(),
+ }
+ }
+
+ const VTABLE: bindings::v4l2_ioctl_ops = bindings::v4l2_ioctl_ops {
+ vidioc_querycap: if T::HAS_QUERYCAP {
+ Some(Self::vidioc_querycap_callback)
+ } else {
+ None
+ },
+ // SAFETY: All zeros is a valid value for `bindings::v4l2_ioctl_ops`.
+ ..unsafe { MaybeUninit::zeroed().assume_init() }
+ };
+
+ pub(super) const fn build() -> &'static bindings::v4l2_ioctl_ops {
+ &Self::VTABLE
+ }
+}
diff --git a/rust/kernel/media/v4l2/mod.rs b/rust/kernel/media/v4l2/mod.rs
index 1d8241f8a2230954371965bb91b20e726f144dce..4caa9fdc1ff3ed2bd6145cc653b49a84873ecac2 100644
--- a/rust/kernel/media/v4l2/mod.rs
+++ b/rust/kernel/media/v4l2/mod.rs
@@ -15,3 +15,6 @@
pub mod caps;
/// Support for Video for Linux 2 (V4L2) file handles.
pub mod file;
+
+/// Support for Video for Linux 2 (V4L2) ioctls.
+pub mod ioctl;
diff --git a/rust/kernel/media/v4l2/video.rs b/rust/kernel/media/v4l2/video.rs
index c0ac99a8234d2f7a8effd4701b9f7440236540c8..2390a93a39925dbff3f809bc65adfac1d881309c 100644
--- a/rust/kernel/media/v4l2/video.rs
+++ b/rust/kernel/media/v4l2/video.rs
@@ -19,7 +19,7 @@
use crate::{
alloc,
- error::to_result,
+ error::{to_result, VTABLE_DEFAULT_ERROR},
media::v4l2::{self, caps::DeviceCaps, file::DriverFile, video},
prelude::*,
types::{ARef, AlwaysRefCounted, Opaque},
@@ -143,6 +143,7 @@ unsafe impl<T: Driver> Sync for Device<T> {}
/// The interface that must be implemented by structs that would otherwise embed
/// a C [`struct video_device`](srctree/include/media/v4l2-dev.h).
+#[vtable]
pub trait Driver: v4l2::device::Driver {
/// The type of the driver's private data.
type Data;
@@ -161,6 +162,15 @@ pub trait Driver: v4l2::device::Driver {
/// The capabilities offered by this device node.
const CAPS: DeviceCaps;
+
+ /// Driver implementation for the `querycap` ioctl.
+ fn querycap(
+ _file: &v4l2::file::File<<Self as Driver>::File>,
+ _data: &<Self as Driver>::Data,
+ _cap: &mut v4l2::caps::Capabilities,
+ ) -> Result {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
}
struct DeviceOptions<'a, T: Driver> {
@@ -184,6 +194,7 @@ fn into_raw(self) -> bindings::video_device {
release: Some(Device::<T>::release_callback),
fops: super::file::FileVtable::<T::File>::build(),
device_caps: T::CAPS.as_raw(),
+ ioctl_ops: super::ioctl::IoctlVtable::<T>::build(),
// SAFETY: All zeros is valid for the rest of the fields in this C
// type.
..unsafe { MaybeUninit::zeroed().assume_init() }
--
2.50.1
Powered by blists - more mailing lists