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 3/7] rust: v4l2: add support for video device nodes
Date: Mon, 18 Aug 2025 02:49:49 -0300	[thread overview]
Message-ID: <20250818-v4l2-v1-3-6887e772aac2@collabora.com> (raw)
In-Reply-To: <20250818-v4l2-v1-0-6887e772aac2@collabora.com>

Video device nodes back the actual /dev/videoX files. They expose a rich
ioctl interface for which we will soon add support for and allow for
modelling complex hardware through a topology of nodes, each modelling a
particular hardware function or component.

V4l2 drivers rely on video device nodes pretty extensively, so add a
minimal Rust abstraction for them. The abstraction currently does the
bare-minimum to let users register a V4L2 device node. It also
introduces the video::Driver trait that will be implemented by Rust v4l2
drivers. This trait will then be refined in future patches.

Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>
---
 rust/helpers/v4l2-device.c       |  16 +++
 rust/kernel/media/v4l2/device.rs |   1 -
 rust/kernel/media/v4l2/mod.rs    |   3 +
 rust/kernel/media/v4l2/video.rs  | 251 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 270 insertions(+), 1 deletion(-)

diff --git a/rust/helpers/v4l2-device.c b/rust/helpers/v4l2-device.c
index d19b46e8283ce762b4259e3df5ecf8bb18e863e9..0ead52b9a1ccc0fbc4d7df63578b334b17c05b70 100644
--- a/rust/helpers/v4l2-device.c
+++ b/rust/helpers/v4l2-device.c
@@ -6,3 +6,19 @@ void rust_helper_v4l2_device_get(struct v4l2_device *v4l2_dev)
 {
     v4l2_device_get(v4l2_dev);
 }
+
+void rust_helper_video_get(struct video_device *vdev)
+{
+    get_device(&vdev->dev);
+}
+
+void rust_helper_video_put(struct video_device *vdev)
+{
+    put_device(&vdev->dev);
+}
+
+int rust_helper_video_register_device(struct video_device *vdev,
+                                      enum vfl_devnode_type type, int nr)
+{
+    return video_register_device(vdev, type, nr);
+}
diff --git a/rust/kernel/media/v4l2/device.rs b/rust/kernel/media/v4l2/device.rs
index 26096672e6f6d35711ff9bdabf4d7b20f697a4ab..cbbf07ab63b7725cafecb89eb93c497e749287e7 100644
--- a/rust/kernel/media/v4l2/device.rs
+++ b/rust/kernel/media/v4l2/device.rs
@@ -44,7 +44,6 @@ impl<T: Driver> Device<T> {
     ///
     /// - `ptr` must be a valid pointer to a `struct v4l2_device` that must
     ///   remain valid for the lifetime 'a.
-    #[expect(dead_code)]
     pub(super) unsafe fn from_raw<'a>(ptr: *mut bindings::v4l2_device) -> &'a Device<T> {
         // SAFETY: `ptr` is a valid pointer to a `struct v4l2_device` as per the
         // safety requirements of this function.
diff --git a/rust/kernel/media/v4l2/mod.rs b/rust/kernel/media/v4l2/mod.rs
index 63394d0322fa1f646f3b23a5fadf2ac34a9f666e..ba1d4b7da8d8887b1604031497c300d7e0609cd2 100644
--- a/rust/kernel/media/v4l2/mod.rs
+++ b/rust/kernel/media/v4l2/mod.rs
@@ -7,3 +7,6 @@
 //! [structure of the V4L2 framework]: https://www.kernel.org/doc/html/latest/driver-api/media/v4l2-intro.html#structure-of-the-v4l2-framework
 /// Support for Video for Linux 2 (V4L2) devices.
 pub mod device;
+
+/// Support for Video for Linux 2 (V4L2) video devices.
+pub mod video;
diff --git a/rust/kernel/media/v4l2/video.rs b/rust/kernel/media/v4l2/video.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e6954d3e6ac65201bd40a0215babb354ae10cd12
--- /dev/null
+++ b/rust/kernel/media/v4l2/video.rs
@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-copyrightText: Copyright (C) 2025 Collabora Ltd.
+
+//! Video for Linux 2 (V4L2) video device support.
+//!
+//! Video device nodes back the actual /dev/videoX files and provide an
+//! interface for userspace to interact with the device, usually via the `ioctl`
+//! syscall.
+//!
+//! They expose a rich ioctl interface and allow for modelling complex hardware
+//! through a topology of nodes, each modelling a particular hardware function
+//! or component.
+//!
+//! C headers: [`include/media/v4l2-dev.h`](srctree/include/media/v4l2-dev.h).
+
+use core::{marker::PhantomData, mem::MaybeUninit, ops::Deref, ptr::NonNull};
+
+use pin_init::PinInit;
+
+use crate::{
+    alloc,
+    error::to_result,
+    media::v4l2::{self, video},
+    prelude::*,
+    types::{ARef, AlwaysRefCounted, Opaque},
+};
+
+/// The type of node that will be exposed to userspace.
+#[repr(u32)]
+pub enum NodeType {
+    /// For video input/output devices.
+    Video = bindings::vfl_devnode_type_VFL_TYPE_VIDEO,
+}
+
+/// Identifies if the video device corresponds to a receiver, a transmitter or a
+/// mem-to-mem device.
+#[repr(u32)]
+pub enum Direction {
+    /// The device is a receiver.
+    Rx,
+    /// The device is a transmitter.
+    Tx,
+    // TODO: m2m. We do not support this at the moment, so it is not possible to
+    // specify it here as well.
+}
+
+/// A V4L2 device node.
+///
+/// V4L2 devices nodes are backed by a [`v4l2::Device`]. Each instance is
+/// associated with a specific video device in the filesystem. A logical device
+/// can be represented by multiple instances of this struct.
+///
+/// # Invariants
+///
+/// - `inner` is a valid pointer to a `struct video_device`.
+#[pin_data]
+#[repr(C)]
+pub struct Device<T: Driver> {
+    #[pin]
+    inner: Opaque<bindings::video_device>,
+    #[pin]
+    data: <T as Driver>::Data,
+}
+
+impl<T: Driver> Device<T> {
+    pub(super) fn as_raw(&self) -> *mut bindings::video_device {
+        self.inner.get()
+    }
+
+    /// Converts a raw pointer to a `Device<T>` reference.
+    ///
+    /// # Safety
+    ///
+    /// - `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.
+        unsafe { &*(ptr.cast::<Device<T>>()) }
+    }
+
+    /// Returns the video device number, i.e.: /dev/videoX.
+    pub fn num(&self) -> u16 {
+        // SAFETY: Safe as per the invariants of `Device`.
+        unsafe { (*self.as_raw()).num }
+    }
+
+    /// # Safety
+    ///
+    /// This function must be called as the release callback of `struct
+    /// video_device`.
+    unsafe extern "C" fn release_callback(dev: *mut bindings::video_device) {
+        // SAFETY: `dev` was set by calling `KBox::into_raw` on a
+        // `Pin<KBox<Device<T>>` in `Registration::new`. Now that the refcount
+        // reached zero, we are reassembling the KBox so it can be dropped.
+        let v4l2_dev: Pin<KBox<Device<T>>> =
+            unsafe { Pin::new_unchecked(Box::from_raw(dev.cast())) };
+
+        drop(v4l2_dev)
+    }
+}
+
+impl<T: Driver> AsRef<v4l2::device::Device<T>> for Device<T> {
+    fn as_ref(&self) -> &v4l2::device::Device<T> {
+        // SAFETY: `self.as_raw()` is a valid pointer to a `struct video_device`
+        // as per the invariants of `Device<T>`.
+        unsafe { v4l2::device::Device::from_raw((*self.as_raw()).v4l2_dev) }
+    }
+}
+
+impl<T: Driver> Deref for Device<T> {
+    type Target = <T as Driver>::Data;
+
+    fn deref(&self) -> &Self::Target {
+        &self.data
+    }
+}
+
+/// SAFETY: V4L2 device nodes are always reference counted and the get/put
+/// functions satisfy the requirements.
+unsafe impl<T: Driver> AlwaysRefCounted for Device<T> {
+    fn inc_ref(&self) {
+        // SAFETY: it is safe to call `bindings::video_get` because
+        // `self.inner` is a valid pointer to a `struct video_device` as per
+        // the invariants of `Device<T>`.
+        unsafe { bindings::video_get(self.inner.get()) }
+    }
+
+    unsafe fn dec_ref(obj: NonNull<Self>) {
+        // SAFETY: The safety requirements guarantee that the refcount is
+        // non-zero.
+        unsafe { bindings::video_put(obj.cast().as_ptr()) };
+    }
+}
+
+// SAFETY: it is safe to send a [`Device`] to another thread. In particular, a
+// [`Device`] can be dropped by any thread.
+unsafe impl<T: Driver> Send for Device<T> {}
+
+// SAFETY: It is safe to send a &Device<T> to another thread, as we do not allow
+// mutation through a shared reference.
+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).
+pub trait Driver: v4l2::device::Driver {
+    /// The type of the driver's private data.
+    type Data;
+
+    /// The [`NodeType`] to use when registering the device node.
+    const NODE_TYPE: NodeType;
+
+    /// The [`Direction`] to use when registering the device node.
+    const DIRECTION: Direction;
+
+    /// The name to use when registering the device node.
+    const NAME: &'static CStr;
+}
+
+struct DeviceOptions<'a, T: Driver> {
+    dev: &'a v4l2::device::Device<T>,
+    _phantom: PhantomData<T>,
+}
+
+impl<'a, T: Driver> DeviceOptions<'a, T> {
+    /// Creates a `video_device` ready for registration.
+    fn into_raw(self) -> bindings::video_device {
+        bindings::video_device {
+            v4l2_dev: self.dev.as_raw(),
+            name: {
+                let mut name: [c_char; 64] = [0; 64];
+                let src = T::NAME.as_bytes();
+                let len = core::cmp::min(src.len(), name.len());
+                name[..len].copy_from_slice(&src[..len]);
+                name
+            },
+            vfl_dir: T::DIRECTION as c_uint,
+            release: Some(Device::<T>::release_callback),
+            // SAFETY: All zeros is valid for the rest of the fields in this C
+            // type.
+            ..unsafe { MaybeUninit::zeroed().assume_init() }
+        }
+    }
+}
+
+/// Represents the registration of a V4L2 device node.
+pub struct Registration<T: Driver>(ARef<Device<T>>);
+
+impl<T: Driver> Registration<T> {
+    /// Returns a new `Registration` for the given device, which guarantees that
+    /// the underlying device node is properly initialized and registered, which
+    /// means that it can be safely used.
+    pub fn new(
+        dev: &v4l2::device::Device<T>,
+        data: impl PinInit<<T as Driver>::Data, Error>,
+        flags: alloc::Flags,
+    ) -> Result<Self> {
+        let video_dev = try_pin_init!(Device {
+            inner <- Opaque::try_ffi_init(move |slot: *mut bindings::video_device| {
+                let opts: DeviceOptions<'_, T> = DeviceOptions {
+                    dev,
+                    _phantom: PhantomData
+                };
+
+                // SAFETY: `DeviceOptions::into_raw` produces a valid
+                // `bindings::video_device` that is ready for registration.
+                unsafe { slot.write(opts.into_raw()) };
+
+
+                // SAFETY: It is OK to call this function on a zeroed
+                // `video_device` and a valid `v4l2::Device` reference.
+                to_result(unsafe { bindings::video_register_device(slot, T::NODE_TYPE as c_uint, -1) })
+            }),
+            data <- data,
+        });
+
+        let video_dev = KBox::pin_init(video_dev, flags)?;
+
+        // SAFETY: We will be passing the ownership to ARef<T>, which treats the
+        // underlying memory as pinned throughout its lifetime.
+        //
+        // This is true because:
+        //
+        // - ARef<T> does not expose a &mut T, so there is no way to move the T
+        // (e.g.: via a `core::mem::swap` or similar).
+        // - ARef<T>'s member functions do not move the T either.
+        let ptr = KBox::into_raw(unsafe { Pin::into_inner_unchecked(video_dev) });
+
+        // SAFETY:
+        //
+        // - the refcount is one, and we are transfering the ownership of that
+        // increment to the ARef.
+        // - `ptr` is non-null as it came from `KBox::into_raw`, so it is safe
+        // to call `NonNulll::new_unchecked`.
+        Ok(Self(unsafe { ARef::from_raw(NonNull::new_unchecked(ptr)) }))
+    }
+
+    /// Returns a reference to the underlying video device.
+    pub fn device(&self) -> &video::Device<T> {
+        &self.0
+    }
+}
+
+impl<T: Driver> Drop for Registration<T> {
+    fn drop(&mut self) {
+        // SAFETY: `self.0` is a valid `video_device` that was registered in
+        // [`Registration::new`].
+        unsafe { bindings::video_unregister_device(self.0.as_raw()) };
+    }
+}

-- 
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 ` Daniel Almeida [this message]
2025-08-18  9:26   ` [PATCH 3/7] rust: v4l2: add support for video device nodes Danilo Krummrich
2025-08-18  5:49 ` [PATCH 4/7] rust: v4l2: add support for v4l2 file handles Daniel Almeida
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-3-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).