[PATCH 4/6] rust: media: videobuf2: add a videobuf2 abstraction

From: Daniel Almeida
Date: Thu Apr 06 2023 - 17:58:40 EST


Add a videobuf2 abstraction. Notably, only vb2_v4l2_buffer is supported
as the subsystem-specific struct.

Signed-off-by: Daniel Almeida <daniel.almeida@xxxxxxxxxxxxx>
---
rust/bindings/bindings_helper.h | 3 +
rust/kernel/media/mod.rs | 1 +
rust/kernel/media/videobuf2/core.rs | 552 ++++++++++++++++++++++++++++
rust/kernel/media/videobuf2/mod.rs | 5 +
4 files changed, 561 insertions(+)
create mode 100644 rust/kernel/media/videobuf2/core.rs
create mode 100644 rust/kernel/media/videobuf2/mod.rs

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 3b3d6fcf110f..3153894f426b 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -36,6 +36,9 @@
#include <linux/uaccess.h>
#include <linux/uio.h>
#include <linux/videodev2.h>
+#include <media/videobuf2-core.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-dma-sg.h>

/* `bindgen` gets confused at certain things. */
const gfp_t BINDINGS_GFP_KERNEL = GFP_KERNEL;
diff --git a/rust/kernel/media/mod.rs b/rust/kernel/media/mod.rs
index 342e66382719..95b11544af8d 100644
--- a/rust/kernel/media/mod.rs
+++ b/rust/kernel/media/mod.rs
@@ -3,3 +3,4 @@
//! Media subsystem

pub mod v4l2;
+pub mod videobuf2;
diff --git a/rust/kernel/media/videobuf2/core.rs b/rust/kernel/media/videobuf2/core.rs
new file mode 100644
index 000000000000..40ab06475a8e
--- /dev/null
+++ b/rust/kernel/media/videobuf2/core.rs
@@ -0,0 +1,552 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Video Buffer 2 Core Framework
+//!
+//! C header: [`include/media/videobuf2-core.h`](../../../../include/media/videobuf2-core.h)
+
+use core::cell::UnsafeCell;
+use core::marker::PhantomData;
+use core::ops::Deref;
+
+use alloc::slice;
+
+use crate::device::RawDevice;
+use crate::error::from_kernel_result;
+use crate::media::v4l2::{enums, mmap};
+use crate::prelude::*;
+use crate::sync::ffi_mutex::FfiMutex;
+use crate::sync::Arc;
+use crate::ForeignOwnable;
+
+/// The queue operations to be implemented by drivers.
+#[vtable]
+pub trait QueueOperations {
+ /// The subsystem-specific data, usually bindings::vb2_v4l2_buffer.
+ type SubsystemSpecificData = bindings::vb2_v4l2_buffer;
+ /// The driver specific data.
+ type DriverSpecificData;
+ /// The private data held in the queue.
+ type PrivateData: ForeignOwnable
+ + Deref<Target = PrivateData<Self::SubsystemSpecificData, Self::DriverSpecificData>>;
+
+ // Note: alloc_devs is TODO
+
+ /// Called as part of the queue_setup() queue operation.
+ fn queue_setup(
+ queue: &QueueRef,
+ private_data: &mut Self::PrivateData,
+ num_buffers: &mut u32,
+ num_planes: &mut u32,
+ sizes: &mut [u32],
+ ) -> Result;
+
+ /// Called as part of the wait_prepare() queue operation.
+ fn wait_prepare(_queue: &QueueRef, _private_data: &mut Self::PrivateData) {}
+
+ /// Called as part of the wait_finish() queue operation.
+ fn wait_finish(_queue: &QueueRef, _private_data: &mut Self::PrivateData) {}
+
+ /// Optional. Called as part of the buf_out_validate() queue operation.
+ fn buf_out_validate(_buffer: &Buffer, _private_data: &mut Self::PrivateData) -> Result {
+ Ok(())
+ }
+
+ /// Called as part of the buf_init() queue operation.
+ fn buf_init(_buffer: &Buffer, _private_data: &mut Self::PrivateData) -> Result;
+ /// Called as part of the buf_prepare() queue operation.
+ fn buf_prepare(_buffer: &Buffer, _private_data: &mut Self::PrivateData) -> Result;
+ /// Called as part of the buf_finish() queue operation.
+ fn buf_finish(_buffer: &Buffer, _private_data: &mut Self::PrivateData) {}
+ /// Called as part of the buf_cleanup() queue operation.
+ fn buf_cleanup(_buffer: &Buffer, _private_data: &mut Self::PrivateData);
+
+ /// Called as part of the prepare_streaming() queue operation.
+ fn prepare_streaming(_queue: &QueueRef, _private_data: &mut Self::PrivateData) -> Result {
+ Ok(())
+ }
+ /// Called as part of the start_streaming() queue operation.
+ fn start_streaming(
+ _queue: &QueueRef,
+ _private_data: &mut Self::PrivateData,
+ _count: u32,
+ ) -> Result;
+
+ /// Called as part of the unprepare_streaming() queue operation.
+ fn unprepare_streaming(_queue: &QueueRef, _private_data: &mut Self::PrivateData) {}
+ /// Called as part of the stop_streaming() queue operation.
+ fn stop_streaming(_queue: &QueueRef, _private_data: &mut Self::PrivateData);
+ /// Called as part of the buf_queue() queue operation.
+ fn buf_queue(_buffer: &Buffer, _private_data: &mut Self::PrivateData);
+ /// Called as part of the buf_request_complete() queue operation.
+ fn buf_request_complete(_buffer: &Buffer, _private_data: &mut Self::PrivateData) {}
+}
+
+type AsPrivateData<T> = <T as QueueOperations>::PrivateData;
+
+struct Vb2OperationsVtable<T: QueueOperations>(PhantomData<T>);
+
+impl<T: QueueOperations> Vb2OperationsVtable<T> {
+ unsafe extern "C" fn queue_setup_callback(
+ q: *mut bindings::vb2_queue,
+ num_buffers: *mut core::ffi::c_uint,
+ num_planes: *mut core::ffi::c_uint,
+ sizes: *mut core::ffi::c_uint,
+ _alloc_devs: *mut *mut bindings::device,
+ ) -> core::ffi::c_int {
+ from_kernel_result! {
+ // SAFETY: `q` is a pointer returned by the kernel and outlives the
+ // QueueRef as QueueRef is dropped by the end of this function
+ let queue = unsafe { QueueRef::from_ptr(q) };
+
+ // SAFETY: into_foreign() was called in Queue::new() and this
+ // function is called because we set the ops during Queue::new().
+ // Furthermore, from_pointer() is only called when Queue is dropped,
+ // at which point we cannot be called anymore because we will have
+ // called vb2_queue_release. There are no other concurrent uses of
+ // the pointer until the guard is dropped.
+ let mut private_data = unsafe { T::PrivateData::borrow_mut((*q).drv_priv) };
+
+ // SAFETY: sizes is known to be of size bindings::VB2_MAX_PLANES as
+ // per videobuf2-core.c::vb2_core_reqbufs()
+ let sizes = unsafe { slice::from_raw_parts_mut(sizes as _, bindings::VB2_MAX_PLANES as usize) };
+
+ // SAFETY: these are both safe, since they're initialized in
+ // videobuf2-core.c::vb2_core_reqbufs() and they are not read from any
+ // other pointer while the reference exists, since they live in the
+ // stack of vb2_core_reqbufs().
+ let num_buffers = unsafe { num_buffers.as_mut().ok_or(EINVAL) }?;
+ let num_planes = unsafe { num_planes.as_mut().ok_or(EINVAL) }?;
+
+ T::queue_setup(&queue, &mut private_data, num_buffers, num_planes, sizes)?;
+ Ok(0)
+ }
+ }
+
+ unsafe extern "C" fn wait_prepare_callback(q: *mut bindings::vb2_queue) {
+ // SAFETY: QueueRef has a shorter lifetime if compared to the lifetime
+ // of q. The pointer is passed in by the kernel and thus it is assumed
+ // valid.
+ let queue = unsafe { QueueRef::from_ptr(q) };
+ // Same safety comments from queue_setup_callback apply.
+ let mut private_data = unsafe { T::PrivateData::borrow_mut((*q).drv_priv) };
+ T::wait_prepare(&queue, &mut private_data);
+ }
+
+ unsafe extern "C" fn wait_finish_callback(q: *mut bindings::vb2_queue) {
+ // Same safety comment from wait_prepare_callback applies.
+ let queue = unsafe { QueueRef::from_ptr(q) };
+ // Same safety comments from queue_setup_callback apply.
+ let mut private_data = unsafe { T::PrivateData::borrow_mut((*q).drv_priv) };
+ T::wait_finish(&queue, &mut private_data);
+ }
+
+ unsafe extern "C" fn buf_out_validate_callback(
+ vb: *mut bindings::vb2_buffer,
+ ) -> core::ffi::c_int {
+ from_kernel_result! {
+ // SAFETY: Buffer has a shorter lifetime if compared to the lifetime
+ // of vb. The pointer is passed in by the kernel and thus it is assumed
+ // valid.
+ let buf = unsafe { Buffer::from_ptr(vb) };
+ let queue_ptr = buf.queue_ptr();
+
+ // Same safety comments from queue_setup_callback apply.
+ let mut private_data = unsafe { T::PrivateData::borrow_mut((*queue_ptr).drv_priv) };
+
+ T::buf_out_validate(&buf, &mut private_data)?;
+ Ok(0)
+ }
+ }
+
+ unsafe extern "C" fn buf_init_callback(vb: *mut bindings::vb2_buffer) -> core::ffi::c_int {
+ from_kernel_result! {
+ // SAFETY: Buffer has a shorter lifetime if compared to the lifetime
+ // of vb. The pointer is passed in by the kernel and thus it is assumed
+ // valid.
+ let buf = unsafe { Buffer::from_ptr(vb) };
+ let queue_ptr = buf.queue_ptr();
+
+ // Same safety comments from queue_setup_callback apply.
+ let mut private_data = unsafe { T::PrivateData::borrow_mut((*queue_ptr).drv_priv) };
+
+ T::buf_init(&buf, &mut private_data)?;
+ Ok(0)
+ }
+ }
+
+ unsafe extern "C" fn buf_prepare_callback(vb: *mut bindings::vb2_buffer) -> core::ffi::c_int {
+ from_kernel_result! {
+ // SAFETY: Buffer has a shorter lifetime if compared to the lifetime
+ // of vb. The pointer is passed in by the kernel and thus it is assumed
+ // valid.
+ let buf = unsafe { Buffer::from_ptr(vb) };
+ let queue_ptr = buf.queue_ptr();
+
+ // Same safety comments from queue_setup_callback apply.
+ let mut private_data = unsafe { T::PrivateData::borrow_mut((*queue_ptr).drv_priv) };
+
+ T::buf_prepare(&buf, &mut private_data)?;
+ Ok(0)
+ }
+ }
+
+ unsafe extern "C" fn buf_finish_callback(vb: *mut bindings::vb2_buffer) {
+ // SAFETY: Buffer has a shorter lifetime if compared to the lifetime
+ // of vb. The pointer is passed in by the kernel and thus it is assumed
+ // valid.
+ let buf = unsafe { Buffer::from_ptr(vb) };
+ let queue_ptr = buf.queue_ptr();
+
+ // Same safety comments from queue_setup_callback apply.
+ let mut private_data = unsafe { T::PrivateData::borrow_mut((*queue_ptr).drv_priv) };
+
+ T::buf_finish(&buf, &mut private_data);
+ }
+
+ unsafe extern "C" fn buf_cleanup_callback(vb: *mut bindings::vb2_buffer) {
+ // SAFETY: Buffer has a shorter lifetime if compared to the lifetime
+ // of vb. The pointer is passed in by the kernel and thus it is assumed
+ // valid.
+ let buf = unsafe { Buffer::from_ptr(vb) };
+ let queue_ptr = buf.queue_ptr();
+
+ // Same safety comments from queue_setup_callback apply.
+ let mut private_data = unsafe { T::PrivateData::borrow_mut((*queue_ptr).drv_priv) };
+
+ T::buf_cleanup(&buf, &mut private_data);
+ }
+
+ unsafe extern "C" fn prepare_streaming_callback(
+ q: *mut bindings::vb2_queue,
+ ) -> core::ffi::c_int {
+ from_kernel_result! {
+ // SAFETY: QueueRef has a shorter lifetime if compared to the lifetime
+ // of q. The pointer is passed in by the kernel and thus it is assumed
+ // valid.
+ let queue = unsafe { QueueRef::from_ptr(q) };
+ // Same safety comments from queue_setup_callback apply.
+ let mut private_data = unsafe { T::PrivateData::borrow_mut((*q).drv_priv) };
+ T::prepare_streaming(&queue, &mut private_data)?;
+ Ok(0)
+ }
+ }
+
+ unsafe extern "C" fn start_streaming_callback(
+ q: *mut bindings::vb2_queue,
+ count: core::ffi::c_uint,
+ ) -> core::ffi::c_int {
+ from_kernel_result! {
+ // Same safety comments from queue_setup() apply.
+ let queue = unsafe { QueueRef::from_ptr(q) };
+ let mut private_data = unsafe { T::PrivateData::borrow_mut((*q).drv_priv) };
+ T::start_streaming(&queue, &mut private_data, count)?;
+ Ok(0)
+ }
+ }
+
+ unsafe extern "C" fn stop_streaming_callback(q: *mut bindings::vb2_queue) {
+ // Same safety comments from queue_setup() apply.
+ let queue = unsafe { QueueRef::from_ptr(q) };
+ let mut private_data = unsafe { T::PrivateData::borrow_mut((*q).drv_priv) };
+ T::stop_streaming(&queue, &mut private_data);
+ }
+
+ unsafe extern "C" fn unprepare_streaming_callback(q: *mut bindings::vb2_queue) {
+ // SAFETY: QueueRef has a shorter lifetime if compared to the lifetime
+ // of q. The pointer is passed in by the kernel and thus it is assumed
+ // valid.
+ let queue = unsafe { QueueRef::from_ptr(q) };
+ // Same safety comments from queue_setup_callback apply.
+ let mut private_data = unsafe { T::PrivateData::borrow_mut((*q).drv_priv) };
+ T::unprepare_streaming(&queue, &mut private_data);
+ }
+
+ unsafe extern "C" fn buf_queue_callback(vb: *mut bindings::vb2_buffer) {
+ // SAFETY: Buffer has a shorter lifetime if compared to the lifetime
+ // of vb. The pointer is passed in by the kernel and thus it is assumed
+ // valid.
+ let buf = unsafe { Buffer::from_ptr(vb) };
+ let queue_ptr = buf.queue_ptr();
+
+ // Same safety comments from queue_setup_callback apply.
+ let mut private_data = unsafe { T::PrivateData::borrow_mut((*queue_ptr).drv_priv) };
+
+ T::buf_queue(&buf, &mut private_data);
+ }
+
+ unsafe extern "C" fn buf_request_complete_callback(vb: *mut bindings::vb2_buffer) {
+ // SAFETY: Buffer has a shorter lifetime if compared to the lifetime
+ // of vb. The pointer is passed in by the kernel and thus it is assumed
+ // valid.
+ let buf = unsafe { Buffer::from_ptr(vb) };
+ let queue_ptr = buf.queue_ptr();
+
+ // Same safety comments from queue_setup_callback apply.
+ let mut private_data = unsafe { T::PrivateData::borrow_mut((*queue_ptr).drv_priv) };
+
+ T::buf_request_complete(&buf, &mut private_data);
+ }
+
+ const VTABLE: bindings::vb2_ops = bindings::vb2_ops {
+ queue_setup: Some(Self::queue_setup_callback),
+ wait_prepare: if T::HAS_WAIT_PREPARE {
+ Some(Self::wait_prepare_callback)
+ } else {
+ None
+ },
+ wait_finish: if T::HAS_WAIT_FINISH {
+ Some(Self::wait_finish_callback)
+ } else {
+ None
+ },
+ buf_out_validate: if T::HAS_BUF_OUT_VALIDATE {
+ Some(Self::buf_out_validate_callback)
+ } else {
+ None
+ },
+ buf_init: Some(Self::buf_init_callback),
+ buf_prepare: Some(Self::buf_prepare_callback),
+ buf_finish: if T::HAS_BUF_FINISH {
+ Some(Self::buf_finish_callback)
+ } else {
+ None
+ },
+ buf_cleanup: Some(Self::buf_cleanup_callback),
+ start_streaming: Some(Self::start_streaming_callback),
+ stop_streaming: Some(Self::stop_streaming_callback),
+ buf_queue: Some(Self::buf_queue_callback),
+ buf_request_complete: if T::HAS_BUF_REQUEST_COMPLETE {
+ Some(Self::buf_request_complete_callback)
+ } else {
+ None
+ },
+ prepare_streaming: if T::HAS_PREPARE_STREAMING {
+ Some(Self::prepare_streaming_callback)
+ } else {
+ None
+ },
+ unprepare_streaming: if T::HAS_UNPREPARE_STREAMING {
+ Some(Self::unprepare_streaming_callback)
+ } else {
+ None
+ },
+ };
+
+ fn build() -> &'static bindings::vb2_ops {
+ &Self::VTABLE
+ }
+}
+
+/// The IO modes as defined by `struct vb2_io_mode::VB2_*`.
+#[allow(missing_docs)]
+#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
+pub enum IoModes {
+ Mmap = bindings::vb2_io_modes_VB2_MMAP as isize,
+ UserPtr = bindings::vb2_io_modes_VB2_USERPTR as isize,
+ Read = bindings::vb2_io_modes_VB2_READ as isize,
+ Write = bindings::vb2_io_modes_VB2_WRITE as isize,
+ DmaBuf = bindings::vb2_io_modes_VB2_DMABUF as isize,
+}
+
+/// Controls the value assigned to the `mem_ops` member.
+#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
+pub enum MemOps {
+ /// Sets `bindings::vb2_dma_sg_memops`.
+ DmaSg,
+}
+
+/// Represents the driver-specific buffer structure. This must contain the
+/// subsystem-specific structure as its first member. For now, only `struct
+/// vb2_v4l2_buffer` is supported as the subsystem-specific structure.
+#[repr(C)]
+pub struct PrivateData<T, U> {
+ subsystem_specific: T,
+ driver_specific: U,
+}
+
+impl<T, U> PrivateData<T, U> {
+ /// Returns the size of the driver-specific struct.
+ pub fn driver_data_size(&self) -> usize {
+ core::mem::size_of_val(&self.driver_specific)
+ }
+
+ /// Returns the subsystem-specific struct.
+ pub fn subsystem_specific(&self) -> &T {
+ &self.subsystem_specific
+ }
+
+ /// Returns the driver-specific struct.
+ pub fn driver_specific_mut(&mut self) -> &mut U {
+ &mut self.driver_specific
+ }
+}
+
+impl<U> PrivateData<bindings::vb2_v4l2_buffer, U> {
+ /// Creates a new instance.
+ pub fn new(data: U) -> Self {
+ Self {
+ subsystem_specific: Default::default(),
+ driver_specific: data,
+ }
+ }
+}
+
+/// A struct containing the parameters for queue creation.
+#[allow(missing_docs)]
+pub struct QueueCreateInfo<'a, T: QueueOperations, U: RawDevice> {
+ pub buf_type: enums::BufType,
+ pub io_modes: &'a [IoModes],
+ pub dev: Arc<U>,
+ pub timestamp_flags: &'a [mmap::BufferFlag],
+ pub lock: Option<Pin<Box<FfiMutex>>>,
+ pub min_buffers_needed: usize,
+ pub gfp_flags: u32,
+ pub mem_ops: MemOps,
+ pub requires_requests: bool,
+ pub supports_requests: bool,
+ pub private_data: Option<T::PrivateData>,
+}
+
+/// An owned wrapper over `struct vb2_queue`.
+pub struct Queue<T: QueueOperations> {
+ inner: UnsafeCell<bindings::vb2_queue>,
+ has_private_data: bool,
+ _lock: Option<Pin<Box<FfiMutex>>>,
+ _p1: PhantomData<T>,
+}
+
+impl<T: QueueOperations> Queue<T> {
+ /// Creates a new queue.
+ pub fn new<U: RawDevice>(create_info: QueueCreateInfo<'_, T, U>) -> Result<Self> {
+ let QueueCreateInfo {
+ buf_type,
+ io_modes,
+ dev,
+ timestamp_flags,
+ mut lock,
+ min_buffers_needed,
+ gfp_flags,
+ mem_ops,
+ requires_requests,
+ supports_requests,
+ private_data,
+ } = create_info;
+
+ let mut io_modes_val = 0;
+ for io_mode in io_modes {
+ io_modes_val |= *io_mode as u32;
+ }
+
+ let mut timestamp_flags_val = 0;
+ for timestamp_flag in timestamp_flags {
+ timestamp_flags_val |= *timestamp_flag as u32;
+ }
+
+ let (buf_struct_size, drv_priv, has_private_data) = if let Some(private_data) = private_data
+ {
+ (
+ private_data.driver_data_size(),
+ private_data.into_foreign() as *mut _,
+ true,
+ )
+ } else {
+ (0, core::ptr::null_mut(), false)
+ };
+
+ let mut inner = bindings::vb2_queue {
+ type_: buf_type as _,
+ io_modes: io_modes_val,
+ dev: dev.raw_device(),
+ ..Default::default()
+ };
+
+ if let Some(ref mut lock) = lock {
+ let raw_lock = unsafe { lock.as_mut().raw() };
+ inner.lock = raw_lock;
+ }
+
+ inner.drv_priv = drv_priv;
+ inner.buf_struct_size = buf_struct_size as u32;
+ inner.timestamp_flags = timestamp_flags_val;
+ inner.gfp_flags = gfp_flags;
+ inner.min_buffers_needed = min_buffers_needed as u32;
+
+ inner.set_requires_requests(requires_requests as u32);
+ inner.set_supports_requests(supports_requests as u32);
+
+ inner.ops = Vb2OperationsVtable::<T>::build();
+
+ inner.mem_ops = match mem_ops {
+ // SAFETY: this will be called by the C code.
+ MemOps::DmaSg => unsafe { &bindings::vb2_dma_sg_memops as _ },
+ };
+
+ // SAFETY: just a FFI call and we have just initialized `inner`.
+ unsafe {
+ bindings::vb2_queue_init(&mut inner as _);
+ }
+
+ let inner = UnsafeCell::new(inner);
+
+ Ok(Self {
+ inner,
+ has_private_data,
+ _p1: PhantomData,
+ _lock: lock,
+ })
+ }
+}
+
+impl<T: QueueOperations> Drop for Queue<T> {
+ fn drop(&mut self) {
+ if self.has_private_data {
+ // SAFETY: into_pointer() was previously called during new() and
+ // `self.has_private_data` was set accordingly.
+ unsafe {
+ AsPrivateData::<T>::from_foreign(self.inner.get_mut().drv_priv);
+ }
+ }
+
+ // SAFETY: just a FFI call and we have initialized `inner` during new().
+ unsafe {
+ bindings::vb2_queue_release(self.inner.get());
+ }
+ }
+}
+
+// SAFETY: This is a wrapper over the `struct vb2_queue` C type, which can be
+// used from any thread.
+unsafe impl<T: QueueOperations> Send for Queue<T> {}
+
+/// A wrapper over a pointer to `struct vb2_queue`.
+pub struct QueueRef {
+ _ptr: *mut bindings::vb2_queue,
+}
+
+impl QueueRef {
+ /// # Safety
+ /// The caller must ensure that `ptr` is valid and remains valid for the lifetime of the
+ /// returned [`QueueRef`] instance.
+ unsafe fn from_ptr(ptr: *mut bindings::vb2_queue) -> Self {
+ Self { _ptr: ptr }
+ }
+}
+
+/// A wrapper over a pointer to `struct vb2_buffer`.
+pub struct Buffer {
+ ptr: *mut bindings::vb2_buffer,
+}
+
+impl Buffer {
+ /// # Safety
+ /// The caller must ensure that `ptr` is valid and remains valid for the lifetime of the
+ /// returned [`Buffer`] instance.
+ pub(crate) unsafe fn from_ptr(ptr: *mut bindings::vb2_buffer) -> Self {
+ Self { ptr }
+ }
+
+ pub(crate) fn queue_ptr(&self) -> *mut bindings::vb2_queue {
+ // SAFETY: self.ptr should be valid according to the safety requirements in `from_raw()`.
+ unsafe { (*self.ptr).vb2_queue }
+ }
+}
diff --git a/rust/kernel/media/videobuf2/mod.rs b/rust/kernel/media/videobuf2/mod.rs
new file mode 100644
index 000000000000..6b0be2c7a14d
--- /dev/null
+++ b/rust/kernel/media/videobuf2/mod.rs
@@ -0,0 +1,5 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Videobuf2 Framework
+
+pub mod core;
--
2.40.0