rust-for-linux.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Daniel Almeida <daniel.almeida@collabora.com>
To: "Miguel Ojeda" <ojeda@kernel.org>,
	"Alex Gaynor" <alex.gaynor@gmail.com>,
	"Boqun Feng" <boqun.feng@gmail.com>,
	"Gary Guo" <gary@garyguo.net>,
	"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
	"Benno Lossin" <lossin@kernel.org>,
	"Andreas Hindborg" <a.hindborg@kernel.org>,
	"Alice Ryhl" <aliceryhl@google.com>,
	"Trevor Gross" <tmgross@umich.edu>,
	"Danilo Krummrich" <dakr@kernel.org>,
	"Alexandre Courbot" <acourbot@nvidia.com>
Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org,
	 kernel@collabora.com, linux-media@vger.kernel.org,
	 Daniel Almeida <daniel.almeida@collabora.com>
Subject: [PATCH 4/7] rust: v4l2: add support for v4l2 file handles
Date: Mon, 18 Aug 2025 02:49:50 -0300	[thread overview]
Message-ID: <20250818-v4l2-v1-4-6887e772aac2@collabora.com> (raw)
In-Reply-To: <20250818-v4l2-v1-0-6887e772aac2@collabora.com>

V4L2 allow multiple opens for a variety of use-cases, where the actual
meaning of opening a node more than once is defined by the driver.

Each open call is associated with its own v4l2_fh instance, and these
instances are collected at the struct video_device level, making up a
private context that can be retrieved at later calls, like ioctl()s
performed on the node's file descriptor.

Add basic support for v4l2_fh and a corresponding DriverFile trait to
help model it. This will be needed in future patches in order to add
v4l2 ioctl support, among other things.

Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>
---
 rust/bindings/bindings_helper.h |   1 +
 rust/helpers/v4l2-device.c      |   6 ++
 rust/kernel/media/v4l2/file.rs  | 165 ++++++++++++++++++++++++++++++++++++++++
 rust/kernel/media/v4l2/mod.rs   |   3 +
 rust/kernel/media/v4l2/video.rs |   7 +-
 5 files changed, 180 insertions(+), 2 deletions(-)

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 95651c4bc9e561d9f4949111961f41e65d8c1585..11ff2c018515e99c8af9ad91ab09f8f15ae224c1 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -76,6 +76,7 @@
 #include <linux/workqueue.h>
 #include <linux/xarray.h>
 #include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
 #include <trace/events/rust_sample.h>
 
 #if defined(CONFIG_DRM_PANIC_SCREEN_QR_CODE)
diff --git a/rust/helpers/v4l2-device.c b/rust/helpers/v4l2-device.c
index 0ead52b9a1ccc0fbc4d7df63578b334b17c05b70..24fa41fe570d1263313e70325c4b39f819cb4177 100644
--- a/rust/helpers/v4l2-device.c
+++ b/rust/helpers/v4l2-device.c
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 
+#include <media/v4l2-dev.h>
 #include <media/v4l2-device.h>
 
 void rust_helper_v4l2_device_get(struct v4l2_device *v4l2_dev)
@@ -22,3 +23,8 @@ int rust_helper_video_register_device(struct video_device *vdev,
 {
     return video_register_device(vdev, type, nr);
 }
+
+struct video_device *rust_helper_video_devdata(struct file *filp)
+{
+    return video_devdata(filp);
+}
diff --git a/rust/kernel/media/v4l2/file.rs b/rust/kernel/media/v4l2/file.rs
new file mode 100644
index 0000000000000000000000000000000000000000..37b34f8e6f251fafde5f7e6b4bd654519d8247a5
--- /dev/null
+++ b/rust/kernel/media/v4l2/file.rs
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-copyrightText: Copyright (C) 2025 Collabora Ltd.
+
+//! V4L2 file handle support.
+//!
+//! V4L2 allow multiple opens for a variety of use-cases, where the actual
+//! meaning of opening a node more than once is defined by the driver.
+//!
+//! Each open call is associated with its own v4l2_fh instance, and these
+//! instances are collected at the struct video_device level, making up a
+//! private context that can be retrieved at later calls, like ioctl()s
+//! performed on the node's file descriptor.
+
+use core::{marker::PhantomData, mem::MaybeUninit, ops::Deref};
+
+use pin_init::PinInit;
+
+use crate::{alloc::KBox, init::InPlaceInit, media::v4l2::video, prelude::*, types::Opaque};
+
+/// Trait that must be implemented by V4L2 drivers to represent a V4L2 file.
+pub trait DriverFile {
+    /// The parent `Driver` implementation for this `DriverFile`.
+    type Driver: video::Driver;
+
+    /// A reference to the module that this driver belongs to.
+    const MODULE: &'static ThisModule;
+
+    /// Called when a client opens a V4L2 node.
+    fn open(vdev: &video::Device<Self::Driver>) -> impl PinInit<Self, Error>;
+}
+
+/// An open V4L2 file handle.
+///
+/// # Invariants
+///
+/// `inner` is a valid instance of a `struct v4l2_fh`.
+#[repr(C)]
+#[pin_data]
+pub struct File<T: DriverFile> {
+    #[pin]
+    inner: Opaque<bindings::v4l2_fh>,
+    #[pin]
+    driver_file: T,
+}
+
+impl<T: DriverFile> File<T> {
+    /// Converts a raw pointer to a `File<T>` reference.
+    ///
+    /// # Safety
+    ///
+    /// - `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.
+        unsafe { &*(ptr.cast::<File<T>>()) }
+    }
+
+    /// Returns the raw pointer to the `struct v4l2_fh`.
+    pub(super) fn as_raw(&self) -> *mut bindings::v4l2_fh {
+        self.inner.get()
+    }
+
+    /// The open callback of a `struct video_device`
+    ///
+    /// # Safety
+    ///
+    /// - this function must be called as the open callback of a `struct video_device`.
+    /// - `raw_file` must be a valid pointer to a `struct file`.
+    pub(super) unsafe extern "C" fn open_callback(raw_file: *mut bindings::file) -> c_int {
+        // SAFETY: `raw_file` is a valid pointer to a `struct file` and this is
+        // the open callback for a `struct video device` as per the safety
+        // requirements. This means that the `struct video_device` can be
+        // retrieved by this FFI call.
+        let vdev = unsafe { bindings::video_devdata(raw_file) };
+
+        let file = try_pin_init!(File::<T> {
+            inner <- Opaque::ffi_init(move |slot: *mut bindings::v4l2_fh| {
+                // SAFETY:
+                // - it is safe for the initializer to write to the slot,
+                // - zeroed() is a valid value for `struct v4l2_fh`.
+                unsafe { *slot = MaybeUninit::zeroed().assume_init() };
+
+                // SAFETY: `slot` was zero-initialized and `vdev` is a valid
+                // video device that was retrieved by `video_devdata`.
+                unsafe { bindings::v4l2_fh_init(slot, vdev) }
+            }),
+            driver_file <- {
+                // SAFETY: `vdev` is a valid pointer to a `struct video_device` that was
+                // retrieved using `video_devdata`. The underlying `struct video_device`
+                // remains valid for the lifetime 'a, i.e.: for the entire scope of this
+                // function.
+                let vdev = unsafe { video::Device::from_raw(vdev) };
+                T::open(vdev)
+            }
+        });
+
+        let file = match KBox::pin_init(file, GFP_KERNEL) {
+            Ok(file) => file,
+            Err(e) => return e.to_errno(),
+        };
+
+        let v4l2_fh = file.as_ref().as_raw();
+
+        // SAFETY: We are passing ownership of the `File<T>` to the kernel,
+        // which treats the underlying memory as pinned throughout its lifetime.
+        let ptr = KBox::into_raw(unsafe { Pin::into_inner_unchecked(file) });
+
+        // SAFETY: `raw_file` points to a valid `struct file` as per the safety
+        // requirements, so it is safe to dereference it.
+        unsafe { (*raw_file).private_data = ptr.cast() };
+
+        // SAFETY: `v4l2_fh` was initialized above in `v4l2_fh_init`.
+        unsafe { bindings::v4l2_fh_add(v4l2_fh) };
+
+        0
+    }
+
+    /// The release callback of a `struct v4l2_fh`.
+    ///
+    /// # Safety
+    ///
+    /// - this function must be called as the release callback of a `struct v4l2_fh`.
+    /// - `raw_file` must be a valid pointer to a `struct file`.
+    pub(super) unsafe extern "C" fn release_callback(raw_file: *mut bindings::file) -> c_int {
+        // SAFETY: `raw_file::private_data` was set to `Pin<KBox<File<T>` in
+        // `open_callback`.
+        let file: Pin<KBox<File<T>>> =
+            unsafe { Pin::new_unchecked(Box::from_raw((*raw_file).private_data.cast())) };
+
+        // SAFETY: `file.inner` is a valid pointer to a `struct v4l2_fh`.
+        unsafe { bindings::v4l2_fh_del(file.as_raw()) };
+        // SAFETY: `file.inner` is a valid pointer to a `struct v4l2_fh`.
+        unsafe { bindings::v4l2_fh_exit(file.as_raw()) };
+
+        0
+    }
+}
+
+impl<T: DriverFile> Deref for File<T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        &self.driver_file
+    }
+}
+
+pub(super) struct FileVtable<T: DriverFile>(PhantomData<T>);
+
+impl<T: DriverFile> FileVtable<T> {
+    const VTABLE: bindings::v4l2_file_operations = bindings::v4l2_file_operations {
+        open: Some(File::<T>::open_callback),
+        release: Some(File::<T>::release_callback),
+        owner: T::MODULE.as_ptr(),
+        unlocked_ioctl: Some(bindings::video_ioctl2),
+        // SAFETY: All zeros is valid for the rest of the fields in this C
+        // type.
+        ..unsafe { MaybeUninit::zeroed().assume_init() }
+    };
+
+    pub(super) const fn build() -> &'static bindings::v4l2_file_operations {
+        &Self::VTABLE
+    }
+}
diff --git a/rust/kernel/media/v4l2/mod.rs b/rust/kernel/media/v4l2/mod.rs
index ba1d4b7da8d8887b1604031497c300d7e0609cd2..1195c18f1336891c4b9b194d4e7e5cd40989ace9 100644
--- a/rust/kernel/media/v4l2/mod.rs
+++ b/rust/kernel/media/v4l2/mod.rs
@@ -10,3 +10,6 @@
 
 /// Support for Video for Linux 2 (V4L2) video devices.
 pub mod video;
+
+/// Support for Video for Linux 2 (V4L2) file handles.
+pub mod file;
diff --git a/rust/kernel/media/v4l2/video.rs b/rust/kernel/media/v4l2/video.rs
index e6954d3e6ac65201bd40a0215babb354ae10cd12..7ef2111c32ca55a2bced8325cd883b28204dc3ee 100644
--- a/rust/kernel/media/v4l2/video.rs
+++ b/rust/kernel/media/v4l2/video.rs
@@ -20,7 +20,7 @@
 use crate::{
     alloc,
     error::to_result,
-    media::v4l2::{self, video},
+    media::v4l2::{self, file::DriverFile, video},
     prelude::*,
     types::{ARef, AlwaysRefCounted, Opaque},
 };
@@ -73,7 +73,6 @@ pub(super) fn as_raw(&self) -> *mut bindings::video_device {
     ///
     /// - `ptr` must be a valid pointer to a `struct video_device` that must
     ///   remain valid for the lifetime 'a.
-    #[expect(dead_code)]
     pub(super) unsafe fn from_raw<'a>(ptr: *mut bindings::video_device) -> &'a Device<T> {
         // SAFETY: `ptr` is a valid pointer to a `struct video_device` as per the
         // safety requirements of this function.
@@ -148,6 +147,9 @@ pub trait Driver: v4l2::device::Driver {
     /// The type of the driver's private data.
     type Data;
 
+    /// The driver's file type.
+    type File: DriverFile;
+
     /// The [`NodeType`] to use when registering the device node.
     const NODE_TYPE: NodeType;
 
@@ -177,6 +179,7 @@ fn into_raw(self) -> bindings::video_device {
             },
             vfl_dir: T::DIRECTION as c_uint,
             release: Some(Device::<T>::release_callback),
+            fops: super::file::FileVtable::<T::File>::build(),
             // SAFETY: All zeros is valid for the rest of the fields in this C
             // type.
             ..unsafe { MaybeUninit::zeroed().assume_init() }

-- 
2.50.1


  parent reply	other threads:[~2025-08-18  5:52 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-08-18  5:49 [PATCH 0/7] rust: add initial v4l2 support Daniel Almeida
2025-08-18  5:49 ` [PATCH 1/7] rust: media: add the media module Daniel Almeida
2025-08-18  8:56   ` Miguel Ojeda
2025-08-18 10:28   ` Janne Grunau
2025-08-18  5:49 ` [PATCH 2/7] rust: v4l2: add support for v4l2_device Daniel Almeida
2025-08-18  9:14   ` Danilo Krummrich
2025-08-18  5:49 ` [PATCH 3/7] rust: v4l2: add support for video device nodes Daniel Almeida
2025-08-18  9:26   ` Danilo Krummrich
2025-08-18  5:49 ` Daniel Almeida [this message]
2025-08-18  5:49 ` [PATCH 5/7] rust: v4l2: add device capabilities Daniel Almeida
2025-08-20  4:14   ` Elle Rhumsaa
2025-08-18  5:49 ` [PATCH 6/7] rust: v4l2: add basic ioctl support Daniel Almeida
2025-08-20  4:22   ` Elle Rhumsaa
2025-08-18  5:49 ` [PATCH 7/7] rust: samples: add the v4l2 sample driver Daniel Almeida
2025-08-20  4:24   ` Elle Rhumsaa
2025-08-20 12:39   ` Danilo Krummrich
2025-08-18  8:45 ` [PATCH 0/7] rust: add initial v4l2 support Miguel Ojeda

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250818-v4l2-v1-4-6887e772aac2@collabora.com \
    --to=daniel.almeida@collabora.com \
    --cc=a.hindborg@kernel.org \
    --cc=acourbot@nvidia.com \
    --cc=alex.gaynor@gmail.com \
    --cc=aliceryhl@google.com \
    --cc=bjorn3_gh@protonmail.com \
    --cc=boqun.feng@gmail.com \
    --cc=dakr@kernel.org \
    --cc=gary@garyguo.net \
    --cc=kernel@collabora.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-media@vger.kernel.org \
    --cc=lossin@kernel.org \
    --cc=ojeda@kernel.org \
    --cc=rust-for-linux@vger.kernel.org \
    --cc=tmgross@umich.edu \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).