[PATCH 6/6] rust: media: add v4l2 rust sample

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


Add a v4l2 rust sample. The sample driver showcases the current support
available in the media module. It also proves that the device will
indeed probe by printing a message to the terminal indicating that
no error took place.

Signed-off-by: Daniel Almeida <daniel.almeida@xxxxxxxxxxxxx>
---
samples/rust/Kconfig | 11 ++
samples/rust/Makefile | 1 +
samples/rust/rust_v4l2.rs | 403 ++++++++++++++++++++++++++++++++++++++
3 files changed, 415 insertions(+)
create mode 100644 samples/rust/rust_v4l2.rs

diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig
index 189c10ced6d4..4bdea8210ae0 100644
--- a/samples/rust/Kconfig
+++ b/samples/rust/Kconfig
@@ -163,4 +163,15 @@ config SAMPLE_RUST_SELFTESTS

If unsure, say N.

+config SAMPLE_RUST_V4L2
+ tristate "V4L2 driver"
+ depends on V4L_PLATFORM_DRIVERS
+ depends on VIDEO_DEV
+ select VIDEOBUF2_DMA_SG
+
+ help
+ This option builds the V4L2 example.
+
+ If unsure, say N.
+
endif # SAMPLES_RUST
diff --git a/samples/rust/Makefile b/samples/rust/Makefile
index 420bcefeb082..5ffa621f3968 100644
--- a/samples/rust/Makefile
+++ b/samples/rust/Makefile
@@ -15,5 +15,6 @@ obj-$(CONFIG_SAMPLE_RUST_NETFILTER) += rust_netfilter.o
obj-$(CONFIG_SAMPLE_RUST_ECHO_SERVER) += rust_echo_server.o
obj-$(CONFIG_SAMPLE_RUST_FS) += rust_fs.o
obj-$(CONFIG_SAMPLE_RUST_SELFTESTS) += rust_selftests.o
+obj-$(CONFIG_SAMPLE_RUST_V4L2) += rust_v4l2.o

subdir-$(CONFIG_SAMPLE_RUST_HOSTPROGS) += hostprogs
diff --git a/samples/rust/rust_v4l2.rs b/samples/rust/rust_v4l2.rs
new file mode 100644
index 000000000000..6742af36ac0a
--- /dev/null
+++ b/samples/rust/rust_v4l2.rs
@@ -0,0 +1,403 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Rust V4L2 sample.
+
+use core::cell::UnsafeCell;
+use core::clone::Clone;
+
+use kernel::bindings;
+use kernel::error;
+use kernel::media::v4l2;
+use kernel::media::videobuf2;
+use kernel::platform;
+use kernel::prelude::*;
+use kernel::sync::smutex::Mutex;
+use kernel::sync::Arc;
+
+pub(crate) struct Driver {
+ _reg: Pin<Box<kernel::driver::Registration<kernel::platform::Adapter<Self>>>>,
+ _pdev: Pin<Box<PlatformDevice>>,
+}
+
+impl v4l2::dev::Driver for Driver {
+ type DriverData = Arc<DeviceData>;
+}
+
+pub(crate) struct Registrations {
+ video: Pin<Box<v4l2::dev::VideoRegistration<Driver>>>,
+ v4l2: Pin<Box<v4l2::device::V4l2Registration>>,
+}
+
+type DeviceData = kernel::device::Data<Registrations, (), V4l2Data>;
+
+pub(crate) struct V4l2Data {
+ pub(crate) _device: Arc<kernel::device::Device>,
+ _vb2_queue: Mutex<videobuf2::core::Queue<Driver>>,
+}
+
+#[vtable]
+impl videobuf2::core::QueueOperations for Driver {
+ type DriverSpecificData = Vb2QueueData;
+
+ type PrivateData = Box<videobuf2::core::PrivateData<bindings::vb2_v4l2_buffer, Vb2QueueData>>;
+
+ fn queue_setup(
+ _queue: &videobuf2::core::QueueRef,
+ _private_data: &mut Self::PrivateData,
+ _num_buffers: &mut u32,
+ _num_planes: &mut u32,
+ _sizes: &mut [u32],
+ ) -> Result {
+ let _vb2_queue_data = _private_data.driver_specific_mut();
+ pr_debug!("queue_setup called.\n");
+ Ok(())
+ }
+
+ fn start_streaming(
+ _queue: &videobuf2::core::QueueRef,
+ _private_data: &mut Self::PrivateData,
+ _count: u32,
+ ) -> Result {
+ pr_info!("start_streaming called.\n");
+ Ok(())
+ }
+
+ fn stop_streaming(_queue: &videobuf2::core::QueueRef, _private_data: &mut Self::PrivateData) {
+ pr_info!("stop_streaming called.\n");
+ }
+
+ fn buf_init(
+ _buffer: &videobuf2::core::Buffer,
+ _private_data: &mut Self::PrivateData,
+ ) -> Result {
+ pr_info!("buf_init called.\n");
+ Ok(())
+ }
+
+ fn buf_prepare(
+ _buffer: &videobuf2::core::Buffer,
+ _private_data: &mut Self::PrivateData,
+ ) -> Result {
+ pr_info!("buf_prepare called.\n");
+ Ok(())
+ }
+
+ fn buf_cleanup(_buffer: &videobuf2::core::Buffer, _private_data: &mut Self::PrivateData) {
+ pr_info!("buf_cleanup called.\n");
+ }
+
+ fn buf_queue(_buffer: &videobuf2::core::Buffer, _private_data: &mut Self::PrivateData) {
+ pr_info!("buf_queue called.\n");
+ }
+}
+
+pub(crate) struct Vb2QueueData {}
+
+impl platform::Driver for Driver {
+ type Data = Arc<DeviceData>;
+
+ kernel::define_of_id_table! {(), [
+ (kernel::of::DeviceId::Compatible(b"v4l2"), None),
+ ]}
+
+ fn probe(
+ pdev: &mut platform::Device,
+ _: core::option::Option<&Self::IdInfo>,
+ ) -> Result<Self::Data> {
+ let dev = kernel::device::Device::from_dev(pdev);
+
+ pr_debug!("V4L2 Rust Sample Probing!\n");
+
+ let v4l2_reg = v4l2::device::V4l2Registration::new()?;
+
+ let fops = v4l2::dev::V4l2FileOperations {
+ open: Some(bindings::v4l2_fh_open),
+ poll: Some(bindings::vb2_fop_poll),
+ mmap: Some(bindings::vb2_fop_mmap),
+ read: Some(bindings::vb2_fop_read),
+ };
+
+ let ioctls = v4l2::dev::V4l2IoctlOperations {
+ reqbufs: Some(bindings::vb2_ioctl_reqbufs),
+ querybuf: Some(bindings::vb2_ioctl_querybuf),
+ qbuf: Some(bindings::vb2_ioctl_qbuf),
+ expbuf: Some(bindings::vb2_ioctl_expbuf),
+ dqbuf: Some(bindings::vb2_ioctl_dqbuf),
+ create_bufs: Some(bindings::vb2_ioctl_create_bufs),
+ prepare_buf: Some(bindings::vb2_ioctl_prepare_buf),
+ streamon: Some(bindings::vb2_ioctl_streamon),
+ streamoff: Some(bindings::vb2_ioctl_streamoff),
+ // prepare_streaming: None,
+ // unprepare_streaming: None,
+ };
+
+ let video_reg =
+ v4l2::dev::VideoRegistration::<Driver>::new(&dev, v4l2_reg.device(), fops, ioctls)?;
+
+ let dev = Arc::try_new(dev)?;
+
+ let io_modes = [
+ videobuf2::core::IoModes::Mmap,
+ videobuf2::core::IoModes::DmaBuf,
+ ];
+
+ let timestamp_flags = [v4l2::mmap::BufferFlag::TimestampMonotonic];
+
+ let queue_data = Vb2QueueData {};
+
+ let queue_data = Box::try_new(videobuf2::core::PrivateData::new(queue_data))?;
+ let queue_data = Some(queue_data);
+
+ let vb2_mutex = unsafe { kernel::sync::ffi_mutex::FfiMutex::new() };
+ let mut vb2_mutex = Pin::from(Box::try_new(vb2_mutex)?);
+ kernel::ffi_mutex_init!(vb2_mutex.as_mut(), "Vb2Queue::lock");
+
+ let create_info = videobuf2::core::QueueCreateInfo {
+ buf_type: v4l2::enums::BufType::VideoCapture,
+ io_modes: &io_modes,
+ dev: dev.clone(),
+ timestamp_flags: &timestamp_flags,
+ lock: Some(vb2_mutex),
+ min_buffers_needed: 1,
+ gfp_flags: bindings::___GFP_DMA32,
+ mem_ops: videobuf2::core::MemOps::DmaSg,
+ requires_requests: false,
+ supports_requests: false,
+ private_data: queue_data,
+ };
+
+ let vb2_queue = videobuf2::core::Queue::<Self>::new(create_info)?;
+
+ let data = kernel::new_device_data!(
+ Registrations {
+ video: Pin::new(Box::try_new(video_reg).unwrap()),
+ v4l2: Pin::new(Box::try_new(v4l2_reg).unwrap()),
+ },
+ (),
+ V4l2Data {
+ _device: dev,
+ _vb2_queue: kernel::sync::smutex::Mutex::new(vb2_queue),
+ },
+ "V4l2::Registrations"
+ )?;
+
+ let data = Arc::<DeviceData>::from(data);
+ let capabilities = [
+ v4l2::capabilities::Capabilities::VideoCapture,
+ v4l2::capabilities::Capabilities::Streaming,
+ ];
+
+ let mut pinned_regs = data.registrations().ok_or(ENXIO)?;
+ let mut pinned_regs_mut = pinned_regs.as_pinned_mut();
+
+ let dev = kernel::device::Device::from_dev(pdev);
+ kernel::v4l2_device_register!(pinned_regs_mut.v4l2.as_mut(), &dev)?;
+ pr_debug!("Sucessfully registered v4l2 device");
+
+ kernel::video_device_register!(
+ pinned_regs_mut.video.as_mut(),
+ data.clone(),
+ v4l2::dev::VflDevNodeType::Video,
+ -1,
+ &capabilities,
+ )?;
+
+ pr_info!("V4L2 Rust Sample Probed!\n");
+ drop(pinned_regs);
+ Ok(data)
+ }
+}
+
+#[vtable]
+impl v4l2::ioctls::Operations for Driver {
+ type PrivateData = Arc<DeviceData>;
+
+ fn vidioc_querycap(
+ _private_data: &mut Self::PrivateData,
+ _cap: v4l2::capabilities::CapabilitiesRef,
+ ) -> Result {
+ pr_info!("vidioc_querycap called");
+ core::result::Result::Ok(())
+ }
+
+ fn vidioc_enum_fmt_vid_cap(
+ _private_data: &mut Self::PrivateData,
+ mut f: v4l2::format::FormatDescRef,
+ ) -> Result {
+ pr_info!("vidioc_enum_fmt_vid_cap called");
+ // Set NV12 to avoid spam by some apps when this module is loaded.
+ f.set_pixel_format(842094158);
+ core::result::Result::Ok(())
+ }
+
+ fn vidioc_enum_framesizes(
+ _private_data: &mut Self::PrivateData,
+ _fsize: v4l2::framesize::FrameSizeRef,
+ ) -> Result {
+ pr_info!("vidioc_enum_framesizes called");
+ core::result::Result::Ok(())
+ }
+
+ fn vidioc_g_fmt_vid_cap(
+ _private_data: &mut Self::PrivateData,
+ _f: v4l2::format::FormatRef,
+ ) -> Result {
+ pr_info!("vidioc_g_fmt_vid_cap called");
+ core::result::Result::Ok(())
+ }
+
+ fn vidioc_s_fmt_vid_cap(
+ _private_data: &mut Self::PrivateData,
+ _f: v4l2::format::FormatRef,
+ ) -> Result {
+ pr_info!("vidioc_s_fmt_vid_cap called");
+ core::result::Result::Ok(())
+ }
+
+ fn vidioc_try_fmt_vid_cap(
+ _private_data: &mut Self::PrivateData,
+ _f: v4l2::format::FormatRef,
+ ) -> Result {
+ pr_info!("vidioc_try_fmt_vid_cap called");
+ core::result::Result::Ok(())
+ }
+
+ fn vidioc_reqbufs(
+ _private_data: &mut Self::PrivateData,
+ _b: v4l2::mmap::RequestBuffersRef,
+ ) -> Result {
+ pr_info!("vidioc_reqbufs called");
+ core::result::Result::Ok(())
+ }
+
+ fn vidioc_querybuf(_private_data: &mut Self::PrivateData, _b: v4l2::mmap::BufferRef) -> Result {
+ pr_info!("vidioc_querybuf called");
+ core::result::Result::Ok(())
+ }
+
+ fn vidioc_qbuf(_private_data: &mut Self::PrivateData, _b: v4l2::mmap::BufferRef) -> Result {
+ pr_info!("vidioc_qbuf called");
+ core::result::Result::Ok(())
+ }
+
+ fn vidioc_expbuf(
+ _private_data: &mut Self::PrivateData,
+ _e: v4l2::mmap::ExportBufferRef,
+ ) -> Result {
+ pr_info!("vidioc_expbuf called");
+ core::result::Result::Ok(())
+ }
+
+ fn vidioc_dqbuf(_private_data: &mut Self::PrivateData, _b: v4l2::mmap::BufferRef) -> Result {
+ pr_info!("vidioc_dqbuf called");
+ core::result::Result::Ok(())
+ }
+
+ fn vidioc_create_bufs(
+ _private_data: &mut Self::PrivateData,
+ _b: v4l2::mmap::CreateBuffersRef,
+ ) -> Result {
+ pr_info!("vidioc_create_bufs called");
+ core::result::Result::Ok(())
+ }
+
+ fn vidioc_prepare_buf(
+ _private_data: &mut Self::PrivateData,
+ _b: v4l2::mmap::BufferRef,
+ ) -> Result {
+ pr_info!("vidioc_prepare_buf called");
+ core::result::Result::Ok(())
+ }
+
+ fn vidioc_streamon(_private_data: &mut Self::PrivateData, _i: v4l2::enums::BufType) -> Result {
+ pr_info!("vidioc_streamon called");
+ core::result::Result::Ok(())
+ }
+
+ fn vidioc_streamoff(_private_data: &mut Self::PrivateData, _i: v4l2::enums::BufType) -> Result {
+ pr_info!("vidioc_streamoff called");
+ core::result::Result::Ok(())
+ }
+
+ fn vidioc_enum_input(
+ _private_data: &mut Self::PrivateData,
+ _inp: v4l2::inputs::InputRef,
+ ) -> Result {
+ pr_info!("vidioc_enum_input called");
+ core::result::Result::Ok(())
+ }
+
+ fn vidioc_g_input(_private_data: &mut Self::PrivateData, _i: &mut u32) -> Result {
+ pr_info!("vidioc_g_input called");
+ core::result::Result::Ok(())
+ }
+
+ fn vidioc_s_input(_private_data: &mut Self::PrivateData, _i: u32) -> Result {
+ pr_info!("vidioc_s_input called");
+ core::result::Result::Ok(())
+ }
+}
+
+/// An owned platform device registered by a driver. This is useful for virtual
+/// drivers, such as this one, since they will not be probed by matching against
+/// the device tree.
+struct PlatformDevice(core::cell::UnsafeCell<bindings::platform_device>);
+
+// SAFETY: I assume _all_ devices should be OK to be moved to any thread.
+unsafe impl Send for PlatformDevice {}
+// SAFETY: Platform device does not expose its inner FFI type.
+unsafe impl Sync for PlatformDevice {}
+
+impl Drop for PlatformDevice {
+ fn drop(&mut self) {
+ // SAFETY: no references to this type are alive here.
+ let platform_device = self.0.get();
+ // SAFETY: an FFI call to a previously registered device.
+ unsafe { bindings::platform_device_unregister(platform_device) }
+ }
+}
+
+unsafe impl kernel::device::RawDevice for PlatformDevice {
+ fn raw_device(&self) -> *mut bindings::device {
+ // SAFETY: no references to this type are alive here.
+ let platform_device = self.0.get();
+ // SAFETY: pointer is valid since it was previously registered when the
+ // module was registered.
+ unsafe { &mut (*platform_device).dev as _ }
+ }
+}
+
+impl kernel::Module for Driver {
+ fn init(name: &'static CStr, module: &'static ThisModule) -> Result<Self> {
+ // Register as platform driver
+ let reg: Pin<Box<kernel::driver::Registration<kernel::platform::Adapter<Self>>>> =
+ kernel::platform::Registration::new_pinned(name, module)?;
+
+ let mut platform_device = kernel::bindings::platform_device::default();
+ let name = kernel::c_str!("rust_v4l2");
+ platform_device.name = name.as_char_ptr();
+
+ let platform_device = Box::try_new(PlatformDevice(UnsafeCell::new(platform_device)))?;
+ let mut platform_device = Pin::from(platform_device);
+
+ let platform_device = unsafe {
+ let res = bindings::platform_device_register(platform_device.as_mut().0.get());
+
+ error::to_result(res)?;
+ platform_device
+ };
+
+ Ok(Self {
+ _reg: reg,
+ _pdev: platform_device, /* This obviously must be kept alive as well, otherwise massive faults will ensure */
+ })
+ }
+}
+
+module! {
+ type: Driver,
+ name: "rust_v4l2",
+ author: "Daniel Almeida",
+ description: "Minimal Rust V4L2 example driver",
+ license: "GPL",
+}
--
2.40.0