From patchwork Thu Apr 6 21:56:10 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Almeida X-Patchwork-Id: 13204268 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 5A549C77B6C for ; Thu, 6 Apr 2023 21:58:08 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229972AbjDFV5i (ORCPT ); Thu, 6 Apr 2023 17:57:38 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:44440 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229812AbjDFV5F (ORCPT ); Thu, 6 Apr 2023 17:57:05 -0400 Received: from madras.collabora.co.uk (madras.collabora.co.uk [46.235.227.172]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 8679EC166; Thu, 6 Apr 2023 14:56:31 -0700 (PDT) Received: from localhost.localdomain (unknown [IPv6:2804:14d:72b4:8284:32a8:8167:f815:2895]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) (Authenticated sender: dwlsalmeida) by madras.collabora.co.uk (Postfix) with ESMTPSA id 72D8666031D8; Thu, 6 Apr 2023 22:56:27 +0100 (BST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com; s=mail; t=1680818190; bh=TtJ2WnTRRkClhj1i41IWbhWF3uLZCg3n4yAehksz+EQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Ik0lOVuKltSX4y1PfBu6VtFlVFA0pILRH2V1/WCWU/rLrhfJeQ6sta6PLgKugNPvX g5U1EB29IDbYe1DNPCZekG5g8PqFaBPEW0tx5sA94q8zjXezQ1Be1Z22mclr51iY81 rQFPOyedj55Iy4NibjdvwpMAdqn6sC5llmmMLgLU91J5scKfrXCsJ1c9ck34F3zD+A jAW2TmtQ3Qlt030U9zLFQSyNyRwnAqBmDcPijl9aoNeRhb7ozXc1fKti9K/GCiJKuc UNYfIRGGTIeaJaO0K2YRPRUU12D8NItP2cWXVWpuSNwMU1SrBUW08e+B8lkpOAQ4kZ 3nC5PgPdkRbmg== From: Daniel Almeida To: wedsonaf@gmail.com, ojeda@kernel.org, mchehab@kernel.org, hverkuil@xs4all.nl Cc: Daniel Almeida , rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, linux-media@vger.kernel.org, kernel@collabora.com Subject: [PATCH 1/6] rust: media: add the media module Date: Thu, 6 Apr 2023 18:56:10 -0300 Message-Id: <20230406215615.122099-2-daniel.almeida@collabora.com> X-Mailer: git-send-email 2.40.0 In-Reply-To: <20230406215615.122099-1-daniel.almeida@collabora.com> References: <20230406215615.122099-1-daniel.almeida@collabora.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org As part of the initial rust v4l2 support, add the media module to the kernel crate. Signed-off-by: Daniel Almeida --- rust/kernel/lib.rs | 2 ++ rust/kernel/media/mod.rs | 5 +++++ rust/kernel/media/v4l2/mod.rs | 3 +++ 3 files changed, 10 insertions(+) create mode 100644 rust/kernel/media/mod.rs create mode 100644 rust/kernel/media/v4l2/mod.rs diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index c20b37e88ab2..d7e8f3297405 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -48,6 +48,8 @@ pub mod types; #[doc(hidden)] pub use bindings; pub use macros; +#[cfg(CONFIG_MEDIA_SUPPORT)] +pub mod media; #[cfg(CONFIG_ARM_AMBA)] pub mod amba; diff --git a/rust/kernel/media/mod.rs b/rust/kernel/media/mod.rs new file mode 100644 index 000000000000..342e66382719 --- /dev/null +++ b/rust/kernel/media/mod.rs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Media subsystem + +pub mod v4l2; diff --git a/rust/kernel/media/v4l2/mod.rs b/rust/kernel/media/v4l2/mod.rs new file mode 100644 index 000000000000..068dd9b4863d --- /dev/null +++ b/rust/kernel/media/v4l2/mod.rs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +//! Abstractions for include/media/v4l2-*.h From patchwork Thu Apr 6 21:56:11 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Almeida X-Patchwork-Id: 13204271 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id B07D4C77B73 for ; Thu, 6 Apr 2023 21:58:09 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S237553AbjDFV5j (ORCPT ); Thu, 6 Apr 2023 17:57:39 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:44540 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S238829AbjDFV5O (ORCPT ); Thu, 6 Apr 2023 17:57:14 -0400 Received: from madras.collabora.co.uk (madras.collabora.co.uk [46.235.227.172]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 92F9FC645; Thu, 6 Apr 2023 14:56:34 -0700 (PDT) Received: from localhost.localdomain (unknown [IPv6:2804:14d:72b4:8284:32a8:8167:f815:2895]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) (Authenticated sender: dwlsalmeida) by madras.collabora.co.uk (Postfix) with ESMTPSA id 7E52566031E4; Thu, 6 Apr 2023 22:56:30 +0100 (BST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com; s=mail; t=1680818193; bh=hvI9cEvN/Bd2EG3hOaKkNG3kaCy57jal3AVi4mFUeNE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=RZPw+LBV1EEE8dB1adiU9CT/tWC+3BjjHmueBehDpzQRbv7hsli4pf+dQEFQCermb nKXO/i9xky2ccoxsRKtKtXCqWN/nUYKvi4qU1/CpUvHqZcgrX6OlRswI9h1v0JcLvu nRKL6rKEg+6SnbDDPzH/T/0h1GFO+LRXHvXXLGE8rlgGBT30Yfusq07k03PptN38rO 1D36cLL2g4ntrPz58LsCSpUDIrw/R7aUlINCHwq1vijgYbribSwRFvz1WFISE9sZSc JVGsuojSbZge2GizIeNIyQQ+lkiY62BNMS/mCz9OAS3nI3qEUgNvtY1LfTq+spNNhE U/FLYUh5QKY2g== From: Daniel Almeida To: wedsonaf@gmail.com, ojeda@kernel.org, mchehab@kernel.org, hverkuil@xs4all.nl Cc: Daniel Almeida , rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, linux-media@vger.kernel.org, kernel@collabora.com Subject: [PATCH 2/6] rust: media: add initial videodev2.h abstractions Date: Thu, 6 Apr 2023 18:56:11 -0300 Message-Id: <20230406215615.122099-3-daniel.almeida@collabora.com> X-Mailer: git-send-email 2.40.0 In-Reply-To: <20230406215615.122099-1-daniel.almeida@collabora.com> References: <20230406215615.122099-1-daniel.almeida@collabora.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org Add initial videodev2.h abstractions. Signed-off-by: Daniel Almeida --- rust/bindings/bindings_helper.h | 1 + rust/kernel/media/v4l2/capabilities.rs | 80 +++++++++++ rust/kernel/media/v4l2/enums.rs | 135 +++++++++++++++++++ rust/kernel/media/v4l2/format.rs | 178 +++++++++++++++++++++++++ rust/kernel/media/v4l2/framesize.rs | 176 ++++++++++++++++++++++++ rust/kernel/media/v4l2/inputs.rs | 104 +++++++++++++++ rust/kernel/media/v4l2/mmap.rs | 81 +++++++++++ rust/kernel/media/v4l2/mod.rs | 7 + 8 files changed, 762 insertions(+) create mode 100644 rust/kernel/media/v4l2/capabilities.rs create mode 100644 rust/kernel/media/v4l2/enums.rs create mode 100644 rust/kernel/media/v4l2/format.rs create mode 100644 rust/kernel/media/v4l2/framesize.rs create mode 100644 rust/kernel/media/v4l2/inputs.rs create mode 100644 rust/kernel/media/v4l2/mmap.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 048bae2197ac..3b3d6fcf110f 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -35,6 +35,7 @@ #include #include #include +#include /* `bindgen` gets confused at certain things. */ const gfp_t BINDINGS_GFP_KERNEL = GFP_KERNEL; diff --git a/rust/kernel/media/v4l2/capabilities.rs b/rust/kernel/media/v4l2/capabilities.rs new file mode 100644 index 000000000000..4abc5728f12d --- /dev/null +++ b/rust/kernel/media/v4l2/capabilities.rs @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! V4L2 Capabilities +//! +//! Part of the following C header: [`include/linux/videodev2.h`](../../../../include/linux/videodev2.h) + +/// Capabilities as defined by `V4L2_CAP_*`. +#[allow(missing_docs)] +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] +pub enum Capabilities { + VideoCapture = bindings::V4L2_CAP_VIDEO_CAPTURE as isize, + VideoOutput = bindings::V4L2_CAP_VIDEO_OUTPUT as isize, + VideoOverlay = bindings::V4L2_CAP_VIDEO_OVERLAY as isize, + VbiCapture = bindings::V4L2_CAP_VBI_CAPTURE as isize, + VbiOutput = bindings::V4L2_CAP_VBI_OUTPUT as isize, + SlicedVbiCapture = bindings::V4L2_CAP_SLICED_VBI_CAPTURE as isize, + SlicedVbiOutput = bindings::V4L2_CAP_SLICED_VBI_OUTPUT as isize, + RdsCapture = bindings::V4L2_CAP_RDS_CAPTURE as isize, + VideoOutputOverlay = bindings::V4L2_CAP_VIDEO_OUTPUT_OVERLAY as isize, + HwFrequencySeek = bindings::V4L2_CAP_HW_FREQ_SEEK as isize, + RdsOutput = bindings::V4L2_CAP_RDS_OUTPUT as isize, + VideoCaptureMplane = bindings::V4L2_CAP_VIDEO_CAPTURE_MPLANE as isize, + VideoOutputMplane = bindings::V4L2_CAP_VIDEO_OUTPUT_MPLANE as isize, + M2mMplane = bindings::V4L2_CAP_VIDEO_M2M_MPLANE as isize, + M2m = bindings::V4L2_CAP_VIDEO_M2M as isize, + Tuner = bindings::V4L2_CAP_TUNER as isize, + Audio = bindings::V4L2_CAP_AUDIO as isize, + Radio = bindings::V4L2_CAP_RADIO as isize, + Modulator = bindings::V4L2_CAP_MODULATOR as isize, + SdrCapture = bindings::V4L2_CAP_SDR_CAPTURE as isize, + ExtPixFormat = bindings::V4L2_CAP_EXT_PIX_FORMAT as isize, + SdrOutput = bindings::V4L2_CAP_SDR_OUTPUT as isize, + MetaCapture = bindings::V4L2_CAP_META_CAPTURE as isize, + ReadWrite = bindings::V4L2_CAP_READWRITE as isize, + Streaming = bindings::V4L2_CAP_STREAMING as isize, + MetaOutput = bindings::V4L2_CAP_META_OUTPUT as isize, + Touch = bindings::V4L2_CAP_TOUCH as isize, + IoMc = bindings::V4L2_CAP_IO_MC as isize, +} + +/// A wrapper over a pointer to `struct v4l2_capability`. +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] +pub struct CapabilitiesRef(*mut bindings::v4l2_capability); + +impl CapabilitiesRef { + /// # Safety + /// The caller must ensure that `ptr` is valid and remains valid for the lifetime of the + /// returned [`CapabilitiesRef`] instance. + pub unsafe fn from_ptr(ptr: *mut bindings::v4l2_capability) -> Self { + Self(ptr) + } + + // For internal convenience only. + fn as_mut(&mut self) -> &mut bindings::v4l2_capability { + // SAFETY: ptr is safe during the lifetime of [`CapabilitiesRef`] as per + // the safety requirement in `from_ptr()` + unsafe { self.0.as_mut().unwrap() } + } + + /// Sets the `driver` field. + pub fn set_driver(&mut self, driver: &[u8]) { + let this = self.as_mut(); + let len = core::cmp::min(driver.len(), this.driver.len()); + this.driver[0..len].copy_from_slice(&driver[0..len]); + } + + /// Sets the `card` field. + pub fn set_card(&mut self, card: &[u8]) { + let this = self.as_mut(); + let len = core::cmp::min(card.len(), this.card.len()); + this.card[0..len].copy_from_slice(&card[0..len]); + } + + /// Sets the `bus_info` field. + pub fn set_bus_info(&mut self, bus_info: &[u8]) { + let this = self.as_mut(); + let len = core::cmp::min(bus_info.len(), this.bus_info.len()); + this.bus_info[0..len].copy_from_slice(&bus_info[0..len]); + } +} diff --git a/rust/kernel/media/v4l2/enums.rs b/rust/kernel/media/v4l2/enums.rs new file mode 100644 index 000000000000..41397693c208 --- /dev/null +++ b/rust/kernel/media/v4l2/enums.rs @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! V4L2 Enums +//! +//! Part of the following C header: [`include/linux/videodev2.h`](../../../../include/linux/videodev2.h) + +use crate::prelude::EINVAL; + +/// Buffer types as defined by `V4L2_BUF_TYPE_*` +#[allow(missing_docs)] +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] +pub enum BufType { + VideoCapture = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_CAPTURE as isize, + VideoOutput = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OUTPUT as isize, + VideoOverlay = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OVERLAY as isize, + VbiCapture = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VBI_CAPTURE as isize, + VbiOutput = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VBI_OUTPUT as isize, + SlicedVbiCapture = bindings::v4l2_buf_type_V4L2_BUF_TYPE_SLICED_VBI_CAPTURE as isize, + SlicedVbiOutput = bindings::v4l2_buf_type_V4L2_BUF_TYPE_SLICED_VBI_OUTPUT as isize, + VideoOutputOverlay = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY as isize, + VideoCaptureMplane = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE as isize, + VideoOutputMplane = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE as isize, + SdrCapture = bindings::v4l2_buf_type_V4L2_BUF_TYPE_SDR_CAPTURE as isize, + SdrOutput = bindings::v4l2_buf_type_V4L2_BUF_TYPE_SDR_OUTPUT as isize, + MetaCapture = bindings::v4l2_buf_type_V4L2_BUF_TYPE_META_CAPTURE as isize, + MetaOutput = bindings::v4l2_buf_type_V4L2_BUF_TYPE_META_OUTPUT as isize, +} + +impl TryFrom for BufType { + type Error = crate::error::Error; + + fn try_from(value: u32) -> Result { + match value { + bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_CAPTURE => Ok(Self::VideoCapture), + bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OUTPUT => Ok(Self::VideoOutput), + bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OVERLAY => Ok(Self::VideoOverlay), + bindings::v4l2_buf_type_V4L2_BUF_TYPE_VBI_CAPTURE => Ok(Self::VbiCapture), + bindings::v4l2_buf_type_V4L2_BUF_TYPE_VBI_OUTPUT => Ok(Self::VbiOutput), + bindings::v4l2_buf_type_V4L2_BUF_TYPE_SLICED_VBI_CAPTURE => Ok(Self::SlicedVbiCapture), + bindings::v4l2_buf_type_V4L2_BUF_TYPE_SLICED_VBI_OUTPUT => Ok(Self::SlicedVbiOutput), + bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY => { + Ok(Self::VideoOutputOverlay) + } + bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE => { + Ok(Self::VideoCaptureMplane) + } + bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE => { + Ok(Self::VideoOutputMplane) + } + bindings::v4l2_buf_type_V4L2_BUF_TYPE_SDR_CAPTURE => Ok(Self::SdrCapture), + bindings::v4l2_buf_type_V4L2_BUF_TYPE_SDR_OUTPUT => Ok(Self::SdrOutput), + bindings::v4l2_buf_type_V4L2_BUF_TYPE_META_CAPTURE => Ok(Self::MetaCapture), + bindings::v4l2_buf_type_V4L2_BUF_TYPE_META_OUTPUT => Ok(Self::MetaOutput), + _ => Err(EINVAL), + } + } +} + +/// Fields as defined by `V4L2_FIELD_*` +#[allow(missing_docs)] +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] +pub enum Field { + Any = bindings::v4l2_field_V4L2_FIELD_ANY as isize, + None = bindings::v4l2_field_V4L2_FIELD_NONE as isize, + Top = bindings::v4l2_field_V4L2_FIELD_TOP as isize, + Bottom = bindings::v4l2_field_V4L2_FIELD_BOTTOM as isize, + Interlaced = bindings::v4l2_field_V4L2_FIELD_INTERLACED as isize, + SeqTb = bindings::v4l2_field_V4L2_FIELD_SEQ_TB as isize, + SeqBt = bindings::v4l2_field_V4L2_FIELD_SEQ_BT as isize, + Alternate = bindings::v4l2_field_V4L2_FIELD_ALTERNATE as isize, + InterlacedTb = bindings::v4l2_field_V4L2_FIELD_INTERLACED_TB as isize, + InterlacedBt = bindings::v4l2_field_V4L2_FIELD_INTERLACED_BT as isize, +} + +impl TryFrom for Field { + type Error = crate::error::Error; + + fn try_from(value: u32) -> Result { + match value { + bindings::v4l2_field_V4L2_FIELD_ANY => Ok(Self::Any), + bindings::v4l2_field_V4L2_FIELD_NONE => Ok(Self::None), + bindings::v4l2_field_V4L2_FIELD_TOP => Ok(Self::Top), + bindings::v4l2_field_V4L2_FIELD_BOTTOM => Ok(Self::Bottom), + bindings::v4l2_field_V4L2_FIELD_INTERLACED => Ok(Self::Interlaced), + bindings::v4l2_field_V4L2_FIELD_SEQ_TB => Ok(Self::SeqTb), + bindings::v4l2_field_V4L2_FIELD_SEQ_BT => Ok(Self::SeqBt), + bindings::v4l2_field_V4L2_FIELD_ALTERNATE => Ok(Self::Alternate), + bindings::v4l2_field_V4L2_FIELD_INTERLACED_TB => Ok(Self::InterlacedTb), + bindings::v4l2_field_V4L2_FIELD_INTERLACED_BT => Ok(Self::InterlacedBt), + _ => Err(EINVAL), + } + } +} + +/// Colorspaces as defined by `V4L2_COLORSPACE_*` +#[allow(missing_docs)] +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] +pub enum Colorspace { + Default = bindings::v4l2_colorspace_V4L2_COLORSPACE_DEFAULT as isize, + Smpte170m = bindings::v4l2_colorspace_V4L2_COLORSPACE_SMPTE170M as isize, + Smpte240m = bindings::v4l2_colorspace_V4L2_COLORSPACE_SMPTE240M as isize, + Rec709 = bindings::v4l2_colorspace_V4L2_COLORSPACE_REC709 as isize, + Bt878 = bindings::v4l2_colorspace_V4L2_COLORSPACE_BT878 as isize, + SystemM470 = bindings::v4l2_colorspace_V4L2_COLORSPACE_470_SYSTEM_M as isize, + SystemBg470 = bindings::v4l2_colorspace_V4L2_COLORSPACE_470_SYSTEM_BG as isize, + Jpeg = bindings::v4l2_colorspace_V4L2_COLORSPACE_JPEG as isize, + Srgb = bindings::v4l2_colorspace_V4L2_COLORSPACE_SRGB as isize, + Oprgb = bindings::v4l2_colorspace_V4L2_COLORSPACE_OPRGB as isize, + Bt2020 = bindings::v4l2_colorspace_V4L2_COLORSPACE_BT2020 as isize, + Raw = bindings::v4l2_colorspace_V4L2_COLORSPACE_RAW as isize, + DciP3 = bindings::v4l2_colorspace_V4L2_COLORSPACE_DCI_P3 as isize, +} + +impl TryFrom for Colorspace { + type Error = crate::error::Error; + + fn try_from(value: u32) -> Result { + match value { + bindings::v4l2_colorspace_V4L2_COLORSPACE_DEFAULT => Ok(Self::Default), + bindings::v4l2_colorspace_V4L2_COLORSPACE_SMPTE170M => Ok(Self::Smpte170m), + bindings::v4l2_colorspace_V4L2_COLORSPACE_SMPTE240M => Ok(Self::Smpte240m), + bindings::v4l2_colorspace_V4L2_COLORSPACE_REC709 => Ok(Self::Rec709), + bindings::v4l2_colorspace_V4L2_COLORSPACE_BT878 => Ok(Self::Bt878), + bindings::v4l2_colorspace_V4L2_COLORSPACE_470_SYSTEM_M => Ok(Self::SystemM470), + bindings::v4l2_colorspace_V4L2_COLORSPACE_470_SYSTEM_BG => Ok(Self::SystemBg470), + bindings::v4l2_colorspace_V4L2_COLORSPACE_JPEG => Ok(Self::Jpeg), + bindings::v4l2_colorspace_V4L2_COLORSPACE_SRGB => Ok(Self::Srgb), + bindings::v4l2_colorspace_V4L2_COLORSPACE_OPRGB => Ok(Self::Oprgb), + bindings::v4l2_colorspace_V4L2_COLORSPACE_BT2020 => Ok(Self::Bt2020), + bindings::v4l2_colorspace_V4L2_COLORSPACE_RAW => Ok(Self::Raw), + bindings::v4l2_colorspace_V4L2_COLORSPACE_DCI_P3 => Ok(Self::DciP3), + _ => Err(EINVAL), + } + } +} diff --git a/rust/kernel/media/v4l2/format.rs b/rust/kernel/media/v4l2/format.rs new file mode 100644 index 000000000000..83bdd61b5c09 --- /dev/null +++ b/rust/kernel/media/v4l2/format.rs @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! V4L2 Format Enumerations +//! +//! Part of the following C header: [`include/linux/videodev2.h`](../../../../include/linux/videodev2.h) + +use crate::media::v4l2::enums; +use crate::prelude::*; + +/// A wrapper over a pointer to `struct v4l2_fmtdesc`. +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] +pub struct FormatDescRef(*mut bindings::v4l2_fmtdesc); + +impl FormatDescRef { + /// # Safety + /// The caller must ensure that `ptr` is valid and remains valid for the lifetime of the + /// returned [`FormatDescRef`] instance. + pub unsafe fn from_ptr(ptr: *mut bindings::v4l2_fmtdesc) -> Self { + Self(ptr) + } + + fn as_mut(&mut self) -> &mut bindings::v4l2_fmtdesc { + // SAFETY: ptr is safe during the lifetime of [`FormatDescRef`] as per + // the safety requirement in `from_ptr()` + unsafe { self.0.as_mut().unwrap() } + } + + fn as_ref(&self) -> &bindings::v4l2_fmtdesc { + // SAFETY: ptr is safe during the lifetime of [`FormatDescRef`] as per + // the safety requirement in `from_ptr()` + unsafe { self.0.as_mut().unwrap() } + } + + /// Sets the `pixelformat` field. + pub fn set_pixel_format(&mut self, pixel_format: u32) { + let fmt = self.as_mut(); + fmt.pixelformat = pixel_format; + } + + /// Returns the `index` field. + pub fn index(&self) -> u32 { + self.as_ref().index + } + + /// Returns the `type_` field. + pub fn type_(&self) -> u32 { + self.as_ref().type_ + } +} + +/// A wrapper over a pointer to `struct v4l2_format`. +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] +pub struct FormatRef(*mut bindings::v4l2_format); + +impl FormatRef { + /// # Safety + /// The caller must ensure that `ptr` is valid and remains valid for the lifetime of the + /// returned [`FormatRef`] instance. + pub unsafe fn from_ptr(ptr: *mut bindings::v4l2_format) -> Self { + Self(ptr) + } + + // For internal convenience only. + fn as_mut(&mut self) -> &mut bindings::v4l2_format { + // SAFETY: ptr is safe during the lifetime of [`FormatDescRef`] as per + // the safety requirement in `from_ptr()` + unsafe { self.0.as_mut().unwrap() } + } + + fn as_ref(&self) -> &bindings::v4l2_format { + // SAFETY: ptr is safe during the lifetime of [`FormatDescRef`] as per + // the safety requirement in `from_ptr()` + unsafe { self.0.as_mut().unwrap() } + } + + /// Returns the `type_` field. + pub fn type_(&self) -> u32 { + self.as_ref().type_ + } + + /// Get the field `field` for the `pix` union member. + pub fn pix_field(&self) -> Result { + let fmt = self.as_ref(); + let pix = &unsafe { fmt.fmt.pix }; + enums::Field::try_from(pix.field) + } + + /// Get the field `width` for the `pix` union member. + pub fn pix_width(&self) -> u32 { + let fmt = self.as_ref(); + let pix = &unsafe { fmt.fmt.pix }; + pix.width + } + + /// Get the field `height` for the `pix` union member. + pub fn pix_height(&self) -> u32 { + let fmt = self.as_ref(); + let pix = &unsafe { fmt.fmt.pix }; + pix.height + } + + /// Get the field `pixelformat` for the `pix` union member. + pub fn pix_pixelformat(&self) -> u32 { + let fmt = self.as_ref(); + let pix = &unsafe { fmt.fmt.pix }; + pix.pixelformat + } + + /// Get the field `bytesperline` for the `pix` union member. + pub fn pix_bytesperline(&self) -> u32 { + let fmt = self.as_ref(); + let pix = &unsafe { fmt.fmt.pix }; + pix.bytesperline + } + + /// Get the field `sizeimage` for the `pix` union member. + pub fn pix_sizeimage(&self) -> u32 { + let fmt = self.as_ref(); + let pix = &unsafe { fmt.fmt.pix }; + pix.sizeimage + } + + /// Get the field `colorspace` for the `pix` union member. + pub fn pix_colorspace(&self) -> Result { + let fmt = self.as_ref(); + let pix = &unsafe { fmt.fmt.pix }; + enums::Colorspace::try_from(pix.colorspace) + } + + /// Set the field `field` for the `pix` union member. + pub fn set_pix_field(&mut self, field: enums::Field) { + let fmt = self.as_mut(); + let pix = &mut unsafe { fmt.fmt.pix }; + pix.field = field as u32; + } + + /// Set the field `width` for the `pix` union member. + pub fn set_pix_width(&mut self, width: u32) { + let fmt = self.as_mut(); + let pix = &mut unsafe { fmt.fmt.pix }; + pix.width = width; + } + + /// Set the field `height` for the `pix` union member. + pub fn set_pix_height(&mut self, height: u32) { + let fmt = self.as_mut(); + let pix = &mut unsafe { fmt.fmt.pix }; + pix.height = height; + } + + /// Set the field `pixelformat` for the `pix` union member. + pub fn set_pix_pixel_format(&mut self, pixel_format: u32) { + let fmt = self.as_mut(); + let pix = &mut unsafe { fmt.fmt.pix }; + pix.pixelformat = pixel_format; + } + + /// Set the field `bytesperline` for the `pix` union member. + pub fn set_pix_bytesperline(&mut self, bytesperline: u32) { + let fmt = self.as_mut(); + let pix = &mut unsafe { fmt.fmt.pix }; + pix.bytesperline = bytesperline; + } + + /// Set the field `sizeimage` for the `pix` union member. + pub fn set_pix_sizeimage(&mut self, sizeimage: u32) { + let fmt = self.as_mut(); + let pix = &mut unsafe { fmt.fmt.pix }; + pix.sizeimage = sizeimage; + } + + /// Set the field `sizeimage` for the `pix` union member. + pub fn set_pix_colorspace(&mut self, colorspace: enums::Colorspace) { + let fmt = self.as_mut(); + let pix = &mut unsafe { fmt.fmt.pix }; + pix.colorspace = colorspace as u32; + } +} diff --git a/rust/kernel/media/v4l2/framesize.rs b/rust/kernel/media/v4l2/framesize.rs new file mode 100644 index 000000000000..2d015a7444be --- /dev/null +++ b/rust/kernel/media/v4l2/framesize.rs @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! V4L2 Frame size enumerations +//! +//! Part of the following C header: [`include/linux/videodev2.h`](../../../../include/linux/videodev2.h) + +/// Frame size types as defined in `V4L2_FRMSIZE_TYPE` +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] +#[allow(missing_docs)] +pub enum FrameSizeType { + Discrete = bindings::v4l2_frmsizetypes_V4L2_FRMSIZE_TYPE_DISCRETE as isize, + Continuous = bindings::v4l2_frmsizetypes_V4L2_FRMSIZE_TYPE_CONTINUOUS as isize, + Stepwise = bindings::v4l2_frmsizetypes_V4L2_FRMSIZE_TYPE_STEPWISE as isize, +} + +impl TryFrom for FrameSizeType { + type Error = crate::error::Error; + + fn try_from(value: u32) -> Result { + match value { + bindings::v4l2_frmsizetypes_V4L2_FRMSIZE_TYPE_DISCRETE => Ok(Self::Discrete), + bindings::v4l2_frmsizetypes_V4L2_FRMSIZE_TYPE_CONTINUOUS => Ok(Self::Continuous), + bindings::v4l2_frmsizetypes_V4L2_FRMSIZE_TYPE_STEPWISE => Ok(Self::Stepwise), + _ => Err(crate::prelude::EINVAL), + } + } +} + +/// A wrapper over a pointer to `struct v4l2_frmsizeenum`. +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] +pub struct FrameSizeRef(*mut bindings::v4l2_frmsizeenum); + +impl FrameSizeRef { + /// # Safety + /// The caller must ensure that `ptr` is valid and remains valid for the lifetime of the + /// returned [`FrameSizeRef`] instance. + pub unsafe fn from_ptr(ptr: *mut bindings::v4l2_frmsizeenum) -> Self { + Self(ptr) + } + + fn as_ref(&self) -> &bindings::v4l2_frmsizeenum { + // SAFETY: ptr is safe during the lifetime of [`FrameSizeRef`] as per + // the safety requirement in `from_ptr()` + unsafe { self.0.as_ref().unwrap() } + } + + fn as_mut(&mut self) -> &mut bindings::v4l2_frmsizeenum { + // SAFETY: ptr is safe during the lifetime of [`FrameSizeRef`] as per + // the safety requirement in `from_ptr()` + unsafe { self.0.as_mut().unwrap() } + } + + /// Sets the `index` member. + pub fn set_index(&mut self, index: u32) { + let this = self.as_mut(); + this.index = index; + } + + /// Sets the `pixel_format` member. + pub fn set_pixel_format(&mut self, pixel_format: u32) { + let this = self.as_mut(); + this.pixel_format = pixel_format; + } + + /// Sets the `type_` member. + pub fn set_type(&mut self, type_: FrameSizeType) { + let this = self.as_mut(); + this.type_ = type_ as _; + } + + /// Sets the `width` member from the `discrete` union member. + pub fn set_discrete_width(&mut self, width: u32) { + let this = self.as_mut(); + this.__bindgen_anon_1.discrete.width = width; + } + + /// Sets the `height` member from the `discrete` union member. + pub fn set_discrete_height(&mut self, height: u32) { + let this = self.as_mut(); + this.__bindgen_anon_1.discrete.height = height; + } + + /// Sets the `min_width` member from the `step_wise` union member. + pub fn set_stepwise_min_width(&mut self, min_width: u32) { + let this = self.as_mut(); + this.__bindgen_anon_1.stepwise.min_width = min_width; + } + + /// Sets the `max_width` member from the `step_wise` union member. + pub fn set_stepwise_max_width(&mut self, max_width: u32) { + let this = self.as_mut(); + this.__bindgen_anon_1.stepwise.max_width = max_width; + } + + /// Sets the `step_width` member from the `step_wise` union member. + pub fn set_stepwise_step_width(&mut self, step_width: u32) { + let this = self.as_mut(); + this.__bindgen_anon_1.stepwise.step_width = step_width; + } + + /// Sets the `min_height` member from the `step_wise` union member. + pub fn set_stepwise_min_height(&mut self, min_height: u32) { + let this = self.as_mut(); + this.__bindgen_anon_1.stepwise.min_height = min_height; + } + + /// Sets the `max_height` member from the `step_wise` union member. + pub fn set_stepwise_max_height(&mut self, max_height: u32) { + let this = self.as_mut(); + this.__bindgen_anon_1.stepwise.max_height = max_height; + } + + /// Sets the `step_height` member from the `step_wise` union member. + pub fn set_stepwise_step_height(&mut self, step_height: u32) { + let this = self.as_mut(); + this.__bindgen_anon_1.stepwise.step_height = step_height; + } + + /// Returns the `index` member. + pub fn index(&self) -> u32 { + self.as_ref().index + } + + /// Returns the `pixel_format` member. + pub fn pixel_format(&self) -> u32 { + self.as_ref().pixel_format + } + + /// Returns the `width` member from the `discrete` union member. + pub fn discrete_width(&self) -> u32 { + let this = self.as_ref(); + unsafe { this.__bindgen_anon_1.discrete.width } + } + + /// Returns the `height` member from the `discrete` union member. + pub fn discrete_height(&self) -> u32 { + let this = self.as_ref(); + unsafe { this.__bindgen_anon_1.discrete.height } + } + + /// Returns the `min_width` member from the `step_wise` union member. + pub fn stepwise_min_width(&self) -> u32 { + let this = self.as_ref(); + unsafe { this.__bindgen_anon_1.stepwise.min_width } + } + + /// Returns the `max_width` member from the `step_wise` union member. + pub fn stepwise_max_width(&self) -> u32 { + let this = self.as_ref(); + unsafe { this.__bindgen_anon_1.stepwise.max_width } + } + + /// Returns the `step_width` member from the `step_wise` union member. + pub fn stepwise_step_width(&self) -> u32 { + let this = self.as_ref(); + unsafe { this.__bindgen_anon_1.stepwise.step_width } + } + + /// Returns the `min_height` member from the `step_wise` union member. + pub fn stepwise_min_height(&self) -> u32 { + let this = self.as_ref(); + unsafe { this.__bindgen_anon_1.stepwise.min_height } + } + + /// Returns the `max_height` member from the `step_wise` union member. + pub fn stepwise_max_height(&self) -> u32 { + let this = self.as_ref(); + unsafe { this.__bindgen_anon_1.stepwise.max_height } + } + + /// Returns the `step_height` member from the `step_wise` union member. + pub fn stepwise_step_height(&self) -> u32 { + let this = self.as_ref(); + unsafe { this.__bindgen_anon_1.stepwise.step_height } + } +} diff --git a/rust/kernel/media/v4l2/inputs.rs b/rust/kernel/media/v4l2/inputs.rs new file mode 100644 index 000000000000..8361971e2983 --- /dev/null +++ b/rust/kernel/media/v4l2/inputs.rs @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! V4L2 Video Inputs +//! +//! Part of the following C header: [`include/linux/videodev2.h`](../../../../include/linux/videodev2.h) + +use crate::prelude::*; +use crate::str::CStr; + +/// A wrapper over a pointer to `struct v4l2_input`. +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] +pub struct InputRef(*mut bindings::v4l2_input); + +impl InputRef { + /// # Safety + /// The caller must ensure that `ptr` is valid and remains valid for the lifetime of the + /// returned [`InputRef`] instance. + pub unsafe fn from_ptr(ptr: *mut bindings::v4l2_input) -> Self { + Self(ptr) + } + + fn as_mut(&mut self) -> &mut bindings::v4l2_input { + // SAFETY: ptr is safe during the lifetime of [`InputRef`] as per + // the safety requirement in `from_ptr()` + unsafe { self.0.as_mut().unwrap() } + } + + fn as_ref(&self) -> &bindings::v4l2_input { + // SAFETY: ptr is safe during the lifetime of [`InputRef`] as per + // the safety requirement in `from_ptr()` + unsafe { self.0.as_ref().unwrap() } + } + + /// Returns the `index` member. + pub fn index(&self) -> u32 { + let this = self.as_ref(); + this.index + } + + /// Returns the `name` member. + pub fn name(&self) -> core::result::Result<&CStr, crate::str::CStrConvertError> { + let this = self.as_ref(); + CStr::from_bytes_with_nul(&this.name) + } + + /// Returns the `type_` member. + pub fn type_(&self) -> Result { + let this = self.as_ref(); + Type::try_from(this.type_) + } + + /// Returns the `status` member. + pub fn status(&self) -> u32 { + let this = self.as_ref(); + this.status + } + + /// Sets the `name` member. + pub fn set_name(&mut self, name: &CStr) { + let this = self.as_mut(); + let len = core::cmp::min(name.len(), this.name.len()); + this.name[0..len].copy_from_slice(&name[0..len]); + } + + /// Sets the `type_` member. + pub fn set_type(&mut self, type_: Type) { + let this = self.as_mut(); + this.type_ = type_ as u32; + } + + /// Sets the `std` member. + pub fn set_std(&mut self, std: u64) { + let this = self.as_mut(); + this.std = std; + } + + /// Sets the `status` member. + pub fn set_status(&mut self, status: u32) { + let this = self.as_mut(); + this.status = status; + } +} + +/// Input types as defined by `V4L2_INPUT_TYPE_*` +#[allow(missing_docs)] +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] +pub enum Type { + Tuner = bindings::V4L2_INPUT_TYPE_TUNER as isize, + Camera = bindings::V4L2_INPUT_TYPE_CAMERA as isize, + Touch = bindings::V4L2_INPUT_TYPE_TOUCH as isize, +} + +impl TryFrom for Type { + type Error = crate::error::Error; + + fn try_from(value: u32) -> Result { + match value { + bindings::V4L2_INPUT_TYPE_TUNER => Ok(Self::Tuner), + bindings::V4L2_INPUT_TYPE_CAMERA => Ok(Self::Camera), + bindings::V4L2_INPUT_TYPE_TOUCH => Ok(Self::Touch), + _ => Err(EINVAL), + } + } +} diff --git a/rust/kernel/media/v4l2/mmap.rs b/rust/kernel/media/v4l2/mmap.rs new file mode 100644 index 000000000000..2d9ce2ceb148 --- /dev/null +++ b/rust/kernel/media/v4l2/mmap.rs @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! V4L2 Memory-mapping buffers +//! +//! Part of the following C header: [`include/linux/videodev2.h`](../../../../include/linux/videodev2.h) + +/// Buffer flags as defined by `V4L2_BUF_FLAG_*`. +#[allow(missing_docs)] +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] +pub enum BufferFlag { + Mapped = bindings::V4L2_BUF_FLAG_MAPPED as isize, + Queued = bindings::V4L2_BUF_FLAG_QUEUED as isize, + Done = bindings::V4L2_BUF_FLAG_DONE as isize, + Keyframe = bindings::V4L2_BUF_FLAG_KEYFRAME as isize, + Pframe = bindings::V4L2_BUF_FLAG_PFRAME as isize, + Bframe = bindings::V4L2_BUF_FLAG_BFRAME as isize, + Error = bindings::V4L2_BUF_FLAG_ERROR as isize, + InRequest = bindings::V4L2_BUF_FLAG_IN_REQUEST as isize, + Timecode = bindings::V4L2_BUF_FLAG_TIMECODE as isize, + HoldCaptureBuf = bindings::V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF as isize, + Prepared = bindings::V4L2_BUF_FLAG_PREPARED as isize, + NoCacheInvalidate = bindings::V4L2_BUF_FLAG_NO_CACHE_INVALIDATE as isize, + NoCacheClean = bindings::V4L2_BUF_FLAG_NO_CACHE_CLEAN as isize, + TimestampMonotonic = bindings::V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC as isize, + TimestampCopy = bindings::V4L2_BUF_FLAG_TIMESTAMP_COPY as isize, + TstampSrcSoe = bindings::V4L2_BUF_FLAG_TSTAMP_SRC_SOE as isize, + Last = bindings::V4L2_BUF_FLAG_LAST as isize, + RequestFd = bindings::V4L2_BUF_FLAG_REQUEST_FD as isize, +} + +/// A wrapper over a pointer to `struct v4l2_exportbuffer`. +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] +pub struct ExportBufferRef(*mut bindings::v4l2_exportbuffer); + +impl ExportBufferRef { + /// # Safety + /// The caller must ensure that `ptr` is valid and remains valid for the lifetime of the + /// returned [`ExportBufferRef`] instance. + pub unsafe fn from_ptr(ptr: *mut bindings::v4l2_exportbuffer) -> Self { + Self(ptr) + } +} + +/// A wrapper over a pointer to `struct v4l2_requestbuffers`. +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] +pub struct RequestBuffersRef(*mut bindings::v4l2_requestbuffers); + +impl RequestBuffersRef { + /// # Safety + /// The caller must ensure that `ptr` is valid and remains valid for the lifetime of the + /// returned [`RequestBuffersRef`] instance. + pub unsafe fn from_ptr(ptr: *mut bindings::v4l2_requestbuffers) -> Self { + Self(ptr) + } +} + +/// A wrapper over a pointer to `struct v4l2_create_buffers`. +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] +pub struct CreateBuffersRef(*mut bindings::v4l2_create_buffers); + +impl CreateBuffersRef { + /// # Safety + /// The caller must ensure that `ptr` is valid and remains valid for the lifetime of the + /// returned [`CreateBuffersRef`] instance. + pub unsafe fn from_ptr(ptr: *mut bindings::v4l2_create_buffers) -> Self { + Self(ptr) + } +} + +/// A wrapper over a pointer to `struct v4l2_buffer`. +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] +pub struct BufferRef(*mut bindings::v4l2_buffer); + +impl BufferRef { + /// # Safety + /// The caller must ensure that `ptr` is valid and remains valid for the lifetime of the + /// returned [`BufferRef`] instance. + pub unsafe fn from_ptr(ptr: *mut bindings::v4l2_buffer) -> Self { + Self(ptr) + } +} diff --git a/rust/kernel/media/v4l2/mod.rs b/rust/kernel/media/v4l2/mod.rs index 068dd9b4863d..77864640f19e 100644 --- a/rust/kernel/media/v4l2/mod.rs +++ b/rust/kernel/media/v4l2/mod.rs @@ -1,3 +1,10 @@ // SPDX-License-Identifier: GPL-2.0 OR MIT //! Abstractions for include/media/v4l2-*.h + +pub mod capabilities; +pub mod enums; +pub mod format; +pub mod framesize; +pub mod inputs; +pub mod mmap; From patchwork Thu Apr 6 21:56:12 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Almeida X-Patchwork-Id: 13204270 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 50CAAC77B71 for ; Thu, 6 Apr 2023 21:58:09 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S235839AbjDFV5i (ORCPT ); Thu, 6 Apr 2023 17:57:38 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:44402 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S238783AbjDFV5N (ORCPT ); Thu, 6 Apr 2023 17:57:13 -0400 Received: from madras.collabora.co.uk (madras.collabora.co.uk [46.235.227.172]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 62F12B453; Thu, 6 Apr 2023 14:56:37 -0700 (PDT) Received: from localhost.localdomain (unknown [IPv6:2804:14d:72b4:8284:32a8:8167:f815:2895]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) (Authenticated sender: dwlsalmeida) by madras.collabora.co.uk (Postfix) with ESMTPSA id B759766031D9; Thu, 6 Apr 2023 22:56:33 +0100 (BST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com; s=mail; t=1680818196; bh=Z1mWh6lO7+hdoozjzHbSbRzBrAE3llpM//4UJUMybns=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=QAQepvs3Zbpe0+7Y9i4iiyVuHrSwrwPxURN0SnPqlGdXKKV2xwAcxDJo/g7rthf6F tKTIaCLdCWpceHIn/aD/e2JtpmxEJNGtLOdAsZiA7JxNK0cBxGsFj5+LVQq6+t7cLP cO2vM1NDzL5Cf93rZ/tHahn8YVzTJgGYBf1QcjiaqVgFi+5lqYNu7VHmeLfEZIZ5UD OJEvHEtuYBWTyx/nslLupNS5l+EEkWXRvBy/id14oXNfTExAsgV2Fqf+KFxzPD+1Ow wpTVW+O5X4Kzaykh/HAxMQt+Cp8AlxWHTJb8b0KY3Qzc3RB1D+2aCHESAzurWihORA Olef4SVXq8PYQ== From: Daniel Almeida To: wedsonaf@gmail.com, ojeda@kernel.org, mchehab@kernel.org, hverkuil@xs4all.nl Cc: Daniel Almeida , rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, linux-media@vger.kernel.org, kernel@collabora.com Subject: [PATCH 3/6] rust: sync: introduce FfiMutex Date: Thu, 6 Apr 2023 18:56:12 -0300 Message-Id: <20230406215615.122099-4-daniel.almeida@collabora.com> X-Mailer: git-send-email 2.40.0 In-Reply-To: <20230406215615.122099-1-daniel.almeida@collabora.com> References: <20230406215615.122099-1-daniel.almeida@collabora.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org Introduce an owned mutex that can be passed as bindings::mutex when initializing C data structures. The kernel will be locking it without user intervention so there is no data to protect and no guard type. Signed-off-by: Daniel Almeida --- rust/kernel/sync.rs | 1 + rust/kernel/sync/ffi_mutex.rs | 70 +++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 rust/kernel/sync/ffi_mutex.rs diff --git a/rust/kernel/sync.rs b/rust/kernel/sync.rs index b07db83972ac..1415062be34d 100644 --- a/rust/kernel/sync.rs +++ b/rust/kernel/sync.rs @@ -26,6 +26,7 @@ use core::{cell::UnsafeCell, mem::MaybeUninit, pin::Pin}; mod arc; mod condvar; +pub mod ffi_mutex; mod guard; mod locked_by; mod mutex; diff --git a/rust/kernel/sync/ffi_mutex.rs b/rust/kernel/sync/ffi_mutex.rs new file mode 100644 index 000000000000..e615ec4059f6 --- /dev/null +++ b/rust/kernel/sync/ffi_mutex.rs @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! An owned mutex that can be passed as bindings::mutex when initializing C +//! data structures. The kernel will be locking it without user intervention so +//! there is no data to protect and no guard type. + +#![allow(dead_code)] + +use crate::prelude::*; +use crate::sync::{LockClassKey, LockIniter}; +use core::marker::PhantomPinned; +use core::pin::Pin; + +use crate::Opaque; + +/// An owned mutex that can be passed as bindings::mutex when initializing C +/// data structures. The kernel will be locking it without user intervention so +/// there is no data to protect and no guard type. +pub struct FfiMutex { + mutex: Opaque, + _pin: PhantomPinned, +} + +impl FfiMutex { + /// Constructs a new Mutex for FFI purposes. + /// + /// # Safety + /// + /// The caller must call [`FfiMutex::init_lock`] before using the raw Mutex. + pub const unsafe fn new() -> Self { + Self { + mutex: Opaque::uninit(), + _pin: PhantomPinned, + } + } + + /// Returns the inner bindings::mutex + /// + /// # Safety + /// + /// The caller must call [`FfiMutex::init_lock`] before using the raw Mutex. + pub(crate) unsafe fn raw(self: &mut Pin<&mut Self>) -> *mut bindings::mutex { + let this = self.as_mut(); + // SAFETY: mutex is pinned when Lock is. The argument to the function is not moved. + let this = unsafe { this.map_unchecked_mut(|x| &mut x.mutex) }; + // This does not move from the field. + this.get() + } +} + +impl LockIniter for FfiMutex { + fn init_lock(self: Pin<&mut Self>, name: &'static CStr, key: &'static LockClassKey) { + unsafe { bindings::__mutex_init(self.mutex.get(), name.as_char_ptr(), key.get()) }; + } +} + +// SAFETY: the underlying bindings::mutex can be used from any thread. +unsafe impl Send for FfiMutex {} +// SAFETY: two threads can try locking the underlying bindings::mutex at the +// same thread without issues. +unsafe impl Sync for FfiMutex {} + +/// Safely initialises a [`FfiMutex`] with the given name, generating a new lock +/// class. +#[macro_export] +macro_rules! ffi_mutex_init { + ($mutex:expr, $name:literal) => { + $crate::init_with_lockdep!($mutex, $name); + }; +} From patchwork Thu Apr 6 21:56:13 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Almeida X-Patchwork-Id: 13204272 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 7FB41C77B6C for ; Thu, 6 Apr 2023 21:58:40 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229610AbjDFV6J (ORCPT ); Thu, 6 Apr 2023 17:58:09 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:44922 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S239070AbjDFV5U (ORCPT ); Thu, 6 Apr 2023 17:57:20 -0400 Received: from madras.collabora.co.uk (madras.collabora.co.uk [46.235.227.172]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 06AC0C662; Thu, 6 Apr 2023 14:56:40 -0700 (PDT) Received: from localhost.localdomain (unknown [IPv6:2804:14d:72b4:8284:32a8:8167:f815:2895]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) (Authenticated sender: dwlsalmeida) by madras.collabora.co.uk (Postfix) with ESMTPSA id BC9F266031E5; Thu, 6 Apr 2023 22:56:36 +0100 (BST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com; s=mail; t=1680818199; bh=C96CX0gBykPcUYmkODcP4UC2eE4vLS/sM/+9ysNemDA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=asalYTtqDCip3hexE6lvQt+gOkLosY1QGopaolMtbfe37nTCszZafYOZGJDLmmeib Tgy1h2Wr17gU+xdNQlQJBqSiZPHTpstc7SfTZdo4VULy8HSJGFMq6Yf046juhiqbH+ yXqacDzybyDX+KcI2SXuWsmEIn/PfAawCqoogtQzZ3NdFMMWfeQ+S5GdBqwVXZhhXv pftxSVvbRrmsiMKLOVpGmo3mrTh1iA/+u9N/H2W9BF0kLrFQOjVf0qkVQAJm4fijL5 tn1jWOWFPhCvGUJG27w8NdcDqpF6rI7wf5vpxkk/o6rG6ILpyAy9gQFkX+AnDLjGIG 952lc2lpvU4cg== From: Daniel Almeida To: wedsonaf@gmail.com, ojeda@kernel.org, mchehab@kernel.org, hverkuil@xs4all.nl Cc: Daniel Almeida , rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, linux-media@vger.kernel.org, kernel@collabora.com Subject: [PATCH 4/6] rust: media: videobuf2: add a videobuf2 abstraction Date: Thu, 6 Apr 2023 18:56:13 -0300 Message-Id: <20230406215615.122099-5-daniel.almeida@collabora.com> X-Mailer: git-send-email 2.40.0 In-Reply-To: <20230406215615.122099-1-daniel.almeida@collabora.com> References: <20230406215615.122099-1-daniel.almeida@collabora.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org Add a videobuf2 abstraction. Notably, only vb2_v4l2_buffer is supported as the subsystem-specific struct. Signed-off-by: Daniel Almeida --- 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 #include #include +#include +#include +#include /* `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>; + + // 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 = ::PrivateData; + +struct Vb2OperationsVtable(PhantomData); + +impl Vb2OperationsVtable { + 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 { + subsystem_specific: T, + driver_specific: U, +} + +impl PrivateData { + /// 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 PrivateData { + /// 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, + pub timestamp_flags: &'a [mmap::BufferFlag], + pub lock: Option>>, + 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, +} + +/// An owned wrapper over `struct vb2_queue`. +pub struct Queue { + inner: UnsafeCell, + has_private_data: bool, + _lock: Option>>, + _p1: PhantomData, +} + +impl Queue { + /// Creates a new queue. + pub fn new(create_info: QueueCreateInfo<'_, T, U>) -> Result { + 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::::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 Drop for Queue { + 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::::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 Send for Queue {} + +/// 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; From patchwork Thu Apr 6 21:56:14 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Almeida X-Patchwork-Id: 13204274 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id E7285C76196 for ; Thu, 6 Apr 2023 21:58:40 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230006AbjDFV6L (ORCPT ); Thu, 6 Apr 2023 17:58:11 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:44972 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S239133AbjDFV5W (ORCPT ); Thu, 6 Apr 2023 17:57:22 -0400 Received: from madras.collabora.co.uk (madras.collabora.co.uk [IPv6:2a00:1098:0:82:1000:25:2eeb:e5ab]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id B42B1AD38; Thu, 6 Apr 2023 14:56:43 -0700 (PDT) Received: from localhost.localdomain (unknown [IPv6:2804:14d:72b4:8284:32a8:8167:f815:2895]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) (Authenticated sender: dwlsalmeida) by madras.collabora.co.uk (Postfix) with ESMTPSA id F1F9266031D1; Thu, 6 Apr 2023 22:56:39 +0100 (BST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com; s=mail; t=1680818202; bh=FegfZ3tLOugnBxUw43eQFl1qVBqSNnm9U1uA7lyIrEA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=HbwCP/2MMMRuRUkWV+JJNYUocsBmk8GL7TDbJDRVPQ/Bv2aBgcjjuBndFy0LZHu0e iaUWbagp/w4FqQqCd2+vxcLvd/RRk0UtspNd3oEUWvugqhQhGLfLOc8DttnqI0Igwn yZ7l52dXLHhYfuCbbpCuJJMbhPGVY8TCEVZfqctmTXd3vZSloin/oseo28JD4ZCgfW K9k8RyD13ZY51rnqKElgFOiyEGO/iGYJ+icd/4VdDMbu6yfqh1XbBHocIi4CPXTLHN mwKVMly9F70Pfb6km5HFaG219J8WnvlJbOao8vB+NV/u2sS8rJ9QwtALrqBZDtv1n+ RkEADafoYdF5Q== From: Daniel Almeida To: wedsonaf@gmail.com, ojeda@kernel.org, mchehab@kernel.org, hverkuil@xs4all.nl Cc: Daniel Almeida , rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, linux-media@vger.kernel.org, kernel@collabora.com Subject: [PATCH 5/6] rust: media: add {video|v4l2}_device_register support Date: Thu, 6 Apr 2023 18:56:14 -0300 Message-Id: <20230406215615.122099-6-daniel.almeida@collabora.com> X-Mailer: git-send-email 2.40.0 In-Reply-To: <20230406215615.122099-1-daniel.almeida@collabora.com> References: <20230406215615.122099-1-daniel.almeida@collabora.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org Add {video|v4l2}_device_register support, essentially introducing (initial) support for v4l2 drivers in Rust. Signed-off-by: Daniel Almeida --- rust/bindings/bindings_helper.h | 4 + rust/kernel/media/v4l2/dev.rs | 369 +++++++++++++++++++ rust/kernel/media/v4l2/device.rs | 115 ++++++ rust/kernel/media/v4l2/ioctls.rs | 608 +++++++++++++++++++++++++++++++ rust/kernel/media/v4l2/mod.rs | 3 + 5 files changed, 1099 insertions(+) create mode 100644 rust/kernel/media/v4l2/dev.rs create mode 100644 rust/kernel/media/v4l2/device.rs create mode 100644 rust/kernel/media/v4l2/ioctls.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 3153894f426b..bf2e833e511c 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -36,6 +36,10 @@ #include #include #include +#include +#include +#include +#include #include #include #include diff --git a/rust/kernel/media/v4l2/dev.rs b/rust/kernel/media/v4l2/dev.rs new file mode 100644 index 000000000000..32a7573f3439 --- /dev/null +++ b/rust/kernel/media/v4l2/dev.rs @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! V4L2 Driver Helper API +//! +//! C header: [`include/media/v4l2-dev.h`](../../../../include/media/v4l2-dev.h) + +use core::marker::PhantomData; +use core::pin::Pin; + +use crate::device; +use crate::media::v4l2::capabilities; +use crate::media::v4l2::device::V4l2Device; +use crate::prelude::*; +use crate::sync::smutex::Mutex; +use crate::sync::Arc; +use crate::ForeignOwnable; +use crate::Result; +use crate::ThisModule; + +/// The driver trait to be implemented by driver authors. +pub trait Driver: crate::media::v4l2::ioctls::Operations { + /// The driver's private data. + type DriverData: ForeignOwnable + Sync + Send; +} + +/// VflDevNodeTypes as described by `VFL_TYPE_*` constants. +#[allow(missing_docs)] +pub enum VflDevNodeType { + Video = bindings::vfl_devnode_type_VFL_TYPE_VIDEO as isize, + Vbi = bindings::vfl_devnode_type_VFL_TYPE_VBI as isize, + Radio = bindings::vfl_devnode_type_VFL_TYPE_RADIO as isize, + Subdev = bindings::vfl_devnode_type_VFL_TYPE_SUBDEV as isize, + Sdr = bindings::vfl_devnode_type_VFL_TYPE_SDR as isize, + Touch = bindings::vfl_devnode_type_VFL_TYPE_TOUCH as isize, +} + +/// A Video Device. +pub struct VideoDevice { + /// A raw pointer to `struct video_device` in C. + pub(crate) device: bindings::video_device, + /// A phantom for T. We need T to access the associated constants set by + /// drivers. + pub(crate) _phantom: PhantomData, + /// Associated V4l2Device that must be kept alive while the VideoDevice is + /// in use. + _v4l2_device: Arc>, +} + +impl VideoDevice { + fn new(v4l2_device: Arc>) -> Result { + let mut device = bindings::video_device::default(); + + let mut guard = v4l2_device.as_ref().lock(); + let inner = &mut *guard; + let v4l2_device_ptr = inner.device_mut() as *mut bindings::v4l2_device; + + device.v4l2_dev = v4l2_device_ptr; + drop(guard); + + Ok(Self { + device, + _v4l2_device: v4l2_device, + _phantom: PhantomData, + }) + } + + fn _raw(&self) -> &bindings::video_device { + &self.device + } + + fn raw_mut(&mut self) -> &mut bindings::video_device { + &mut self.device + } +} + +impl Drop for VideoDevice { + fn drop(&mut self) { + unsafe { bindings::video_device_release(self.raw_mut()) } + } +} + +/// Functions to be passed to the `fops` member. +#[derive(Default)] +#[allow(missing_docs)] +pub struct V4l2FileOperations { + pub read: Option< + unsafe extern "C" fn( + arg1: *mut bindings::file, + arg2: *mut core::ffi::c_char, + arg3: usize, + arg4: *mut bindings::loff_t, + ) -> isize, + >, + pub poll: Option< + unsafe extern "C" fn( + arg1: *mut bindings::file, + arg2: *mut bindings::poll_table_struct, + ) -> bindings::__poll_t, + >, + pub mmap: Option< + unsafe extern "C" fn( + arg1: *mut bindings::file, + arg2: *mut bindings::vm_area_struct, + ) -> core::ffi::c_int, + >, + pub open: Option core::ffi::c_int>, +} + +/// Functions to be passed to the `ioctl` member. +#[derive(Default)] +#[allow(missing_docs)] +pub struct V4l2IoctlOperations { + pub reqbufs: Option< + unsafe extern "C" fn( + file: *mut bindings::file, + fh: *mut core::ffi::c_void, + b: *mut bindings::v4l2_requestbuffers, + ) -> core::ffi::c_int, + >, + pub querybuf: Option< + unsafe extern "C" fn( + file: *mut bindings::file, + fh: *mut core::ffi::c_void, + b: *mut bindings::v4l2_buffer, + ) -> core::ffi::c_int, + >, + pub qbuf: Option< + unsafe extern "C" fn( + file: *mut bindings::file, + fh: *mut core::ffi::c_void, + b: *mut bindings::v4l2_buffer, + ) -> core::ffi::c_int, + >, + pub expbuf: Option< + unsafe extern "C" fn( + file: *mut bindings::file, + fh: *mut core::ffi::c_void, + e: *mut bindings::v4l2_exportbuffer, + ) -> core::ffi::c_int, + >, + pub dqbuf: Option< + unsafe extern "C" fn( + file: *mut bindings::file, + fh: *mut core::ffi::c_void, + b: *mut bindings::v4l2_buffer, + ) -> core::ffi::c_int, + >, + pub create_bufs: Option< + unsafe extern "C" fn( + file: *mut bindings::file, + fh: *mut core::ffi::c_void, + b: *mut bindings::v4l2_create_buffers, + ) -> core::ffi::c_int, + >, + pub prepare_buf: Option< + unsafe extern "C" fn( + file: *mut bindings::file, + fh: *mut core::ffi::c_void, + b: *mut bindings::v4l2_buffer, + ) -> core::ffi::c_int, + >, + pub streamon: Option< + unsafe extern "C" fn( + file: *mut bindings::file, + fh: *mut core::ffi::c_void, + i: bindings::v4l2_buf_type, + ) -> core::ffi::c_int, + >, + pub streamoff: Option< + unsafe extern "C" fn( + file: *mut bindings::file, + fh: *mut core::ffi::c_void, + i: bindings::v4l2_buf_type, + ) -> core::ffi::c_int, + >, +} + +/// The video registration struct. See `video_device_register!`. +pub struct VideoRegistration { + device: VideoDevice, + registered: bool, + fops: bindings::v4l2_file_operations, + ioctls: bindings::v4l2_ioctl_ops, +} + +impl VideoRegistration { + /// Creates a new `VideoRegistration`. The driver can manually choose + /// alternate ioctl implementations using the `ioctl_operations` argument. + pub fn new( + _parent: &dyn device::RawDevice, + v4l2_device: Arc>, + file_operations: V4l2FileOperations, + ioctl_operations: V4l2IoctlOperations, + ) -> Result { + let video_device = VideoDevice::new(v4l2_device)?; + + let fops = bindings::v4l2_file_operations { + owner: core::ptr::null_mut(), + read: file_operations.read, + write: None, + poll: file_operations.poll, + unlocked_ioctl: Some(bindings::video_ioctl2), + compat_ioctl32: None, + get_unmapped_area: None, + mmap: file_operations.mmap, + open: file_operations.open, + //file_operations::release() is not mandatory, let's try to release things on drop. + release: None, + }; + + let mut ioctls = *crate::media::v4l2::ioctls::OperationsVtable::::build(); + + if let Some(op) = &ioctl_operations.reqbufs { + ioctls.vidioc_reqbufs = Some(*op); + } + + if let Some(op) = &ioctl_operations.querybuf { + ioctls.vidioc_querybuf = Some(*op); + } + + if let Some(op) = &ioctl_operations.qbuf { + ioctls.vidioc_qbuf = Some(*op); + } + + if let Some(op) = &ioctl_operations.expbuf { + ioctls.vidioc_expbuf = Some(*op); + } + + if let Some(op) = &ioctl_operations.dqbuf { + ioctls.vidioc_dqbuf = Some(*op); + } + + if let Some(op) = &ioctl_operations.create_bufs { + ioctls.vidioc_create_bufs = Some(*op); + } + + if let Some(op) = &ioctl_operations.prepare_buf { + ioctls.vidioc_prepare_buf = Some(*op) + } + + if let Some(op) = &ioctl_operations.streamon { + ioctls.vidioc_streamon = Some(*op); + } + + if let Some(op) = &ioctl_operations.streamoff { + ioctls.vidioc_streamoff = Some(*op); + } + + Ok(Self { + device: video_device, + registered: false, + fops, + ioctls, + }) + } + + /// Registers a video device with the rest of the kernel. + /// + /// Users are encouraged to use the [`video_device_register`] macro because it automatically + /// picks up THIS_MODULE. + pub fn register( + self: Pin<&mut Self>, + data: T::DriverData, + vfl_devnode_type: VflDevNodeType, + nr: i32, + module: &'static ThisModule, + capabilities: &[capabilities::Capabilities], + ) -> Result { + if self.registered { + // Already registered. + return Err(EINVAL); + } + + // SAFETY: We never move out of `this` in this method. + let this = unsafe { self.get_unchecked_mut() }; + let video_device = this.device.raw_mut(); + + let mut caps = 0; + for capability in capabilities { + caps |= *capability as u32; + } + + video_device.device_caps = caps; + + // We can release our resources on drop. + video_device.release = Some(bindings::video_device_release_empty); + + let data_pointer = ::into_foreign(data); + + this.fops.owner = module.0; + video_device.fops = &this.fops; + video_device.ioctl_ops = &this.ioctls; + + // SAFETY: video_device was properly allocated during video_register_device + // Use a single unsafe block based on the safety comment above to avoid + // repetition of unsafe on pointer accesses below. + unsafe { + let ret = bindings::__video_register_device( + video_device, + vfl_devnode_type as _, + nr, + 1, + module.0, + ); + + if ret < 0 { + // Reassemble into ForeignOwnable so it can be dropped. + // SAFETY: `data_pointer` was returned by `into_foreign` above. + T::DriverData::from_foreign(data_pointer); + return Err(Error::from_kernel_errno(ret)); + } + + let video_device_parent = &mut video_device.dev; + video_device_parent.driver_data = data_pointer as _; + } + + this.registered = true; + Ok(()) + } +} + +impl Drop for VideoRegistration { + fn drop(&mut self) { + if self.registered { + // SAFETY: device has been properly initialized as per the type invariant. + let video_device_parent = &mut self.device.raw_mut().dev; + + // Obtain a pointer to this before freeing, so it can be assembled + // back into ForeignOwnable and then dropped. + let data_pointer = video_device_parent.driver_data; + + // SAFETY: pointer was properly allocated in VideoDevice::new() + unsafe { bindings::video_unregister_device(self.device.raw_mut()) } + + // Free data as well. + // SAFETY: `data_pointer` was returned by `into_foreign` during registration. + unsafe { ::from_foreign(data_pointer) }; + } + } +} + +// SAFETY: `Registration` doesn't offer any methods or access to fields when +// shared between threads or CPUs, so it is safe to share it. +unsafe impl Sync for VideoRegistration {} + +// SAFETY: Registration and unregistration from the v4l2 subsystem can happen +// from any thread. Additionally, `T::Data` (which is dropped during +// unregistration) is `Send`, so it is ok to move `Registration` to different +// threads. +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Send for VideoRegistration {} + +/// Registers a video device with the rest of the kernel. +/// +/// It automatically picks up THIS_MODULE. +#[allow(clippy::crate_in_macro_def)] +#[macro_export] +macro_rules! video_device_register { + ($reg:expr, $data:expr, $vfl_devnode_type:expr, $nr:expr, $capabilities:expr $(,)?) => {{ + $crate::media::v4l2::dev::VideoRegistration::register( + $reg, + $data, + $vfl_devnode_type, + $nr, + &crate::THIS_MODULE, + $capabilities, + ) + }}; +} diff --git a/rust/kernel/media/v4l2/device.rs b/rust/kernel/media/v4l2/device.rs new file mode 100644 index 000000000000..d7fafab787a6 --- /dev/null +++ b/rust/kernel/media/v4l2/device.rs @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0 +#![allow(missing_docs)] + +//! V4L2 Device support helper. +//! +//! C header: [`include/media/v4l2-device.h`](../../../../include/media/v4l2-device.h) + +use core::cell::UnsafeCell; +use core::pin::Pin; + +use crate::device; +use crate::error; +use crate::prelude::*; +use crate::sync::smutex::Mutex; +use crate::sync::Arc; +use crate::ThisModule; + +/// A V4L2 device. +pub struct V4l2Device { + /// The underlying `struct v4l2_device` in C. + device: UnsafeCell, +} + +impl V4l2Device { + fn new() -> Self { + Self { + device: Default::default(), + } + } + + pub(crate) fn device_mut(&mut self) -> &mut bindings::v4l2_device { + self.device.get_mut() + } +} + +pub struct V4l2Registration { + device: Arc>, + registered: bool, +} + +impl V4l2Registration { + pub fn new() -> Result { + let device = Arc::try_new(Mutex::new(V4l2Device::new()))?; + Ok(Self { + device, + registered: false, + }) + } + + /// Registers a video device with the rest of the kernel. + /// + /// Users are encouraged to use the [`v4l2_device_register`] macro because it automatically + /// picks up THIS_MODULE. + pub fn register( + self: Pin<&mut Self>, + _module: &'static ThisModule, + parent: &dyn device::RawDevice, + ) -> Result { + if self.registered { + // Already registered. + return Err(EINVAL); + } + + // SAFETY: We never move out of `this` in this method. + let this = unsafe { self.get_unchecked_mut() }; + + let device = UnsafeCell::new(bindings::v4l2_device::default()); + + // SAFETY: We know that `RawDevice` is valid due to its safety + // requirements. Furthermore, `device` has been stack-allocated above + // and thus it is valid. + let res = unsafe { bindings::v4l2_device_register(parent.raw_device(), device.get()) }; + error::to_result(res)?; + + this.device.as_ref().lock().device = device; + this.registered = true; + Ok(()) + } + + pub fn device(&self) -> Arc> { + self.device.clone() + } +} + +impl Drop for V4l2Registration { + fn drop(&mut self) { + if self.registered { + let mut device = self.device.as_ref().lock(); + let v4l2_dev = &mut *device; + + unsafe { bindings::v4l2_device_unregister(v4l2_dev.device.get()) } + } + } +} + +// SAFETY: The only field made available by `Registration` is Send + Sync. +// Other than that, it doesn't offer any methods or access to fields when shared +// between threads or CPUs, so it is safe to share it. +unsafe impl Sync for V4l2Registration {} + +// SAFETY: Registration and unregistration from the v4l2 subsystem can happen +// from any thread. +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Send for V4l2Registration {} + +/// Registers a V4L2 device with the rest of the kernel. +/// +/// It automatically picks up THIS_MODULE. +#[allow(clippy::crate_in_macro_def)] +#[macro_export] +macro_rules! v4l2_device_register { + ($reg:expr, $parent:expr $(,)?) => {{ + $crate::media::v4l2::device::V4l2Registration::register($reg, &crate::THIS_MODULE, $parent) + }}; +} diff --git a/rust/kernel/media/v4l2/ioctls.rs b/rust/kernel/media/v4l2/ioctls.rs new file mode 100644 index 000000000000..7c2b45c45ad6 --- /dev/null +++ b/rust/kernel/media/v4l2/ioctls.rs @@ -0,0 +1,608 @@ +// SPDX-License-Identifier: GPL-2.0 +#![allow(missing_docs)] + +//! V4L2 Ioctls +//! +//! C header: [`include/media/v4l2-ioctls.h`](../../../../include/media/v4l2-ioctls.h) + +use bindings::*; +use core::marker::PhantomData; + +use crate::error::from_kernel_result; +use crate::media::v4l2; +use crate::prelude::*; +use crate::ForeignOwnable; + +#[vtable] +pub trait Operations { + type PrivateData: ForeignOwnable; + + fn vidioc_querycap( + _private_data: &mut Self::PrivateData, + _cap: v4l2::capabilities::CapabilitiesRef, + ) -> Result { + Ok(()) + } + + fn vidioc_enum_fmt_vid_cap( + _private_data: &mut Self::PrivateData, + _f: v4l2::format::FormatDescRef, + ) -> Result { + Ok(()) + } + + fn vidioc_enum_framesizes( + _private_data: &mut Self::PrivateData, + _fsize: v4l2::framesize::FrameSizeRef, + ) -> Result { + Ok(()) + } + + fn vidioc_g_fmt_vid_cap( + _private_data: &mut Self::PrivateData, + _f: v4l2::format::FormatRef, + ) -> Result { + Ok(()) + } + + fn vidioc_s_fmt_vid_cap( + _private_data: &mut Self::PrivateData, + _f: v4l2::format::FormatRef, + ) -> Result { + Ok(()) + } + + fn vidioc_try_fmt_vid_cap( + _private_data: &mut Self::PrivateData, + _f: v4l2::format::FormatRef, + ) -> Result { + Ok(()) + } + + fn vidioc_reqbufs( + _private_data: &mut Self::PrivateData, + _b: v4l2::mmap::RequestBuffersRef, + ) -> Result { + Ok(()) + } + + fn vidioc_querybuf(_private_data: &mut Self::PrivateData, _b: v4l2::mmap::BufferRef) -> Result { + Ok(()) + } + + fn vidioc_qbuf(_private_data: &mut Self::PrivateData, _b: v4l2::mmap::BufferRef) -> Result { + Ok(()) + } + + fn vidioc_expbuf( + _private_data: &mut Self::PrivateData, + _e: v4l2::mmap::ExportBufferRef, + ) -> Result { + Ok(()) + } + + fn vidioc_dqbuf(_private_data: &mut Self::PrivateData, _b: v4l2::mmap::BufferRef) -> Result { + Ok(()) + } + + fn vidioc_create_bufs( + _private_data: &mut Self::PrivateData, + _b: v4l2::mmap::CreateBuffersRef, + ) -> Result { + Ok(()) + } + + fn vidioc_prepare_buf( + _private_data: &mut Self::PrivateData, + _b: v4l2::mmap::BufferRef, + ) -> Result { + Ok(()) + } + + fn vidioc_streamon(_private_data: &mut Self::PrivateData, _i: v4l2::enums::BufType) -> Result { + Ok(()) + } + + fn vidioc_streamoff(_private_data: &mut Self::PrivateData, _i: v4l2::enums::BufType) -> Result { + Ok(()) + } + + fn vidioc_enum_input( + _private_data: &mut Self::PrivateData, + _inp: v4l2::inputs::InputRef, + ) -> Result { + Ok(()) + } + + fn vidioc_g_input(_private_data: &mut Self::PrivateData, _i: &mut u32) -> Result { + Ok(()) + } + + fn vidioc_s_input(_private_data: &mut Self::PrivateData, _i: u32) -> Result { + Ok(()) + } +} + +pub(crate) struct OperationsVtable(PhantomData); + +impl OperationsVtable { + fn private_data(file: *mut file) -> crate::ScopeGuard { + // SAFETY: This was set during the video device registration process. + let video_device = unsafe { bindings::video_devdata(file) }; + let data_ptr = unsafe { (*video_device).dev.driver_data }; + + // SAFETY: This was set during the video device registration process and + // now is being passed in by the kernel. We ensure exclusive access + // while the guard is alive. + unsafe { T::PrivateData::borrow_mut(data_ptr) } + } + + unsafe extern "C" fn vidioc_querycap_callback( + file: *mut file, + _fh: *mut core::ffi::c_void, + cap: *mut v4l2_capability, + ) -> core::ffi::c_int { + from_kernel_result! { + let mut private_data = Self::private_data(file); + + // SAFETY: This pointer is passed in and checked by the kernel. + let cap = unsafe { v4l2::capabilities::CapabilitiesRef::from_ptr(cap) } ; + T::vidioc_querycap(&mut private_data, cap)?; + Ok(0) + } + } + + unsafe extern "C" fn vidioc_enum_fmt_vid_cap_callback( + file: *mut file, + _fh: *mut core::ffi::c_void, + f: *mut v4l2_fmtdesc, + ) -> core::ffi::c_int { + from_kernel_result! { + let mut private_data = Self::private_data(file); + + // SAFETY: This pointer is passed in and checked by the kernel. + let fmt_desc = unsafe { v4l2::format::FormatDescRef::from_ptr(f) } ; + T::vidioc_enum_fmt_vid_cap(&mut private_data, fmt_desc)?; + Ok(0) + } + } + + unsafe extern "C" fn vidioc_enum_framesizes( + file: *mut file, + _fh: *mut core::ffi::c_void, + fsize: *mut v4l2_frmsizeenum, + ) -> core::ffi::c_int { + from_kernel_result! { + let mut private_data = Self::private_data(file); + + // SAFETY: This pointer is passed in and checked by the kernel. + let framesizes = unsafe { v4l2::framesize::FrameSizeRef::from_ptr(fsize) } ; + T::vidioc_enum_framesizes(&mut private_data, framesizes)?; + Ok(0) + } + } + + unsafe extern "C" fn vidioc_g_fmt_vid_cap_callback( + file: *mut file, + _fh: *mut core::ffi::c_void, + f: *mut v4l2_format, + ) -> core::ffi::c_int { + from_kernel_result! { + let mut private_data = Self::private_data(file); + + // SAFETY: This pointer is passed in and checked by the kernel. + let fmt = unsafe { v4l2::format::FormatRef::from_ptr(f) } ; + T::vidioc_g_fmt_vid_cap(&mut private_data, fmt)?; + Ok(0) + } + } + + unsafe extern "C" fn vidioc_s_fmt_vid_cap_callback( + file: *mut file, + _fh: *mut core::ffi::c_void, + f: *mut v4l2_format, + ) -> core::ffi::c_int { + from_kernel_result! { + let mut private_data = Self::private_data(file); + + // SAFETY: This pointer is passed in and checked by the kernel. + let fmt = unsafe { v4l2::format::FormatRef::from_ptr(f) } ; + T::vidioc_s_fmt_vid_cap(&mut private_data, fmt)?; + Ok(0) + } + } + + unsafe extern "C" fn vidioc_try_fmt_vid_cap_callback( + file: *mut file, + _fh: *mut core::ffi::c_void, + f: *mut v4l2_format, + ) -> core::ffi::c_int { + from_kernel_result! { + let mut private_data = Self::private_data(file); + + // SAFETY: This pointer is passed in and checked by the kernel. + let fmt = unsafe { v4l2::format::FormatRef::from_ptr(f) } ; + T::vidioc_try_fmt_vid_cap(&mut private_data, fmt)?; + Ok(0) + } + } + + unsafe extern "C" fn vidioc_reqbufs_callback( + file: *mut file, + _fh: *mut core::ffi::c_void, + b: *mut v4l2_requestbuffers, + ) -> core::ffi::c_int { + from_kernel_result! { + let mut private_data = Self::private_data(file); + + // SAFETY: This pointer is passed in and checked by the kernel. + let rb = unsafe { v4l2::mmap::RequestBuffersRef::from_ptr(b) } ; + T::vidioc_reqbufs(&mut private_data, rb)?; + Ok(0) + } + } + + unsafe extern "C" fn vidioc_querybuf_callback( + file: *mut file, + _fh: *mut core::ffi::c_void, + b: *mut v4l2_buffer, + ) -> core::ffi::c_int { + from_kernel_result! { + let mut private_data = Self::private_data(file); + + // SAFETY: This pointer is passed in and checked by the kernel. + let buf = unsafe { v4l2::mmap::BufferRef::from_ptr(b) } ; + T::vidioc_querybuf(&mut private_data, buf)?; + Ok(0) + } + } + + unsafe extern "C" fn vidioc_qbuf_callback( + file: *mut file, + _fh: *mut core::ffi::c_void, + b: *mut v4l2_buffer, + ) -> core::ffi::c_int { + from_kernel_result! { + let mut private_data = Self::private_data(file); + + // SAFETY: This pointer is passed in and checked by the kernel. + let buf = unsafe { v4l2::mmap::BufferRef::from_ptr(b) } ; + T::vidioc_qbuf(&mut private_data, buf)?; + Ok(0) + } + } + + unsafe extern "C" fn vidioc_expbuf_callback( + file: *mut file, + _fh: *mut core::ffi::c_void, + e: *mut v4l2_exportbuffer, + ) -> core::ffi::c_int { + from_kernel_result! { + let mut private_data = Self::private_data(file); + + // SAFETY: This pointer is passed in and checked by the kernel. + let expbuf = unsafe { v4l2::mmap::ExportBufferRef::from_ptr(e) } ; + T::vidioc_expbuf(&mut private_data, expbuf)?; + Ok(0) + } + } + + unsafe extern "C" fn vidioc_dqbuf_callback( + file: *mut file, + _fh: *mut core::ffi::c_void, + b: *mut v4l2_buffer, + ) -> core::ffi::c_int { + from_kernel_result! { + let mut private_data = Self::private_data(file); + + // SAFETY: This pointer is passed in and checked by the kernel. + let buf = unsafe { v4l2::mmap::BufferRef::from_ptr(b) } ; + T::vidioc_dqbuf(&mut private_data, buf)?; + Ok(0) + } + } + + unsafe extern "C" fn vidioc_create_bufs_callback( + file: *mut file, + _fh: *mut core::ffi::c_void, + b: *mut v4l2_create_buffers, + ) -> core::ffi::c_int { + from_kernel_result! { + let mut private_data = Self::private_data(file); + + // SAFETY: This pointer is passed in and checked by the kernel. + let createbuf = unsafe { v4l2::mmap::CreateBuffersRef::from_ptr(b) } ; + T::vidioc_create_bufs(&mut private_data, createbuf)?; + Ok(0) + } + } + + unsafe extern "C" fn vidioc_prepare_buf_callback( + file: *mut file, + _fh: *mut core::ffi::c_void, + b: *mut v4l2_buffer, + ) -> core::ffi::c_int { + from_kernel_result! { + let mut private_data = Self::private_data(file); + + // SAFETY: This pointer is passed in and checked by the kernel. + let buf = unsafe { v4l2::mmap::BufferRef::from_ptr(b) } ; + T::vidioc_prepare_buf(&mut private_data, buf)?; + Ok(0) + } + } + + unsafe extern "C" fn vidioc_streamon_callback( + file: *mut file, + _fh: *mut core::ffi::c_void, + i: v4l2_buf_type, + ) -> core::ffi::c_int { + from_kernel_result! { + let mut private_data = Self::private_data(file); + + // SAFETY: This pointer is passed in and checked by the kernel. + let buf_type = v4l2::enums::BufType::try_from(i)?; + T::vidioc_streamon(&mut private_data, buf_type)?; + Ok(0) + } + } + + unsafe extern "C" fn vidioc_streamoff_callback( + file: *mut file, + _fh: *mut core::ffi::c_void, + i: v4l2_buf_type, + ) -> core::ffi::c_int { + from_kernel_result! { + let mut private_data = Self::private_data(file); + + // SAFETY: This pointer is passed in and checked by the kernel. + let buf_type = v4l2::enums::BufType::try_from(i)?; + T::vidioc_streamoff(&mut private_data, buf_type)?; + Ok(0) + } + } + + unsafe extern "C" fn vidioc_enum_input_callback( + file: *mut file, + _fh: *mut core::ffi::c_void, + inp: *mut v4l2_input, + ) -> core::ffi::c_int { + from_kernel_result! { + let mut private_data = Self::private_data(file); + + // SAFETY: This pointer is passed in and checked by the kernel. + let input = unsafe { v4l2::inputs::InputRef::from_ptr(inp) }; + T::vidioc_enum_input(&mut private_data, input)?; + Ok(0) + } + } + + unsafe extern "C" fn vidioc_g_input_callback( + file: *mut file, + _fh: *mut core::ffi::c_void, + i: *mut core::ffi::c_uint, + ) -> core::ffi::c_int { + from_kernel_result! { + let mut private_data = Self::private_data(file); + let mut val = 0; + + T::vidioc_g_input(&mut private_data, &mut val)?; + + // SAFETY: This pointer is passed in and checked by the kernel. + unsafe {*i = val; } + Ok(0) + } + } + + unsafe extern "C" fn vidioc_s_input_callback( + file: *mut file, + _fh: *mut core::ffi::c_void, + i: core::ffi::c_uint, + ) -> core::ffi::c_int { + from_kernel_result! { + let mut private_data = Self::private_data(file); + + T::vidioc_s_input(&mut private_data, i)?; + + Ok(0) + } + } + + const VTABLE: bindings::v4l2_ioctl_ops = bindings::v4l2_ioctl_ops { + vidioc_querycap: if T::HAS_VIDIOC_QUERYCAP { + Some(Self::vidioc_querycap_callback) + } else { + None + }, + vidioc_enum_fmt_vid_cap: if T::HAS_VIDIOC_ENUM_FMT_VID_CAP { + Some(Self::vidioc_enum_fmt_vid_cap_callback) + } else { + None + }, + vidioc_enum_fmt_vid_overlay: None, + vidioc_enum_fmt_vid_out: None, + vidioc_enum_fmt_sdr_cap: None, + vidioc_enum_fmt_sdr_out: None, + vidioc_enum_fmt_meta_cap: None, + vidioc_enum_fmt_meta_out: None, + vidioc_g_fmt_vid_cap: if T::HAS_VIDIOC_G_FMT_VID_CAP { + Some(Self::vidioc_g_fmt_vid_cap_callback) + } else { + None + }, + vidioc_g_fmt_vid_overlay: None, + vidioc_g_fmt_vid_out: None, + vidioc_g_fmt_vid_out_overlay: None, + vidioc_g_fmt_vbi_cap: None, + vidioc_g_fmt_vbi_out: None, + vidioc_g_fmt_sliced_vbi_cap: None, + vidioc_g_fmt_sliced_vbi_out: None, + vidioc_g_fmt_vid_cap_mplane: None, + vidioc_g_fmt_vid_out_mplane: None, + vidioc_g_fmt_sdr_cap: None, + vidioc_g_fmt_sdr_out: None, + vidioc_g_fmt_meta_cap: None, + vidioc_g_fmt_meta_out: None, + vidioc_s_fmt_vid_cap: if T::HAS_VIDIOC_S_FMT_VID_CAP { + Some(Self::vidioc_s_fmt_vid_cap_callback) + } else { + None + }, + vidioc_s_fmt_vid_overlay: None, + vidioc_s_fmt_vid_out: None, + vidioc_s_fmt_vid_out_overlay: None, + vidioc_s_fmt_vbi_cap: None, + vidioc_s_fmt_vbi_out: None, + vidioc_s_fmt_sliced_vbi_cap: None, + vidioc_s_fmt_sliced_vbi_out: None, + vidioc_s_fmt_vid_cap_mplane: None, + vidioc_s_fmt_vid_out_mplane: None, + vidioc_s_fmt_sdr_cap: None, + vidioc_s_fmt_sdr_out: None, + vidioc_s_fmt_meta_cap: None, + vidioc_s_fmt_meta_out: None, + vidioc_try_fmt_vid_cap: if T::HAS_VIDIOC_TRY_FMT_VID_CAP { + Some(Self::vidioc_try_fmt_vid_cap_callback) + } else { + None + }, + vidioc_try_fmt_vid_overlay: None, + vidioc_try_fmt_vid_out: None, + vidioc_try_fmt_vid_out_overlay: None, + vidioc_try_fmt_vbi_cap: None, + vidioc_try_fmt_vbi_out: None, + vidioc_try_fmt_sliced_vbi_cap: None, + vidioc_try_fmt_sliced_vbi_out: None, + vidioc_try_fmt_vid_cap_mplane: None, + vidioc_try_fmt_vid_out_mplane: None, + vidioc_try_fmt_sdr_cap: None, + vidioc_try_fmt_sdr_out: None, + vidioc_try_fmt_meta_cap: None, + vidioc_try_fmt_meta_out: None, + vidioc_reqbufs: if T::HAS_VIDIOC_REQBUFS { + Some(Self::vidioc_reqbufs_callback) + } else { + None + }, + vidioc_querybuf: if T::HAS_VIDIOC_QUERYBUF { + Some(Self::vidioc_querybuf_callback) + } else { + None + }, + vidioc_qbuf: if T::HAS_VIDIOC_QBUF { + Some(Self::vidioc_qbuf_callback) + } else { + None + }, + vidioc_expbuf: if T::HAS_VIDIOC_EXPBUF { + Some(Self::vidioc_expbuf_callback) + } else { + None + }, + vidioc_dqbuf: if T::HAS_VIDIOC_DQBUF { + Some(Self::vidioc_dqbuf_callback) + } else { + None + }, + vidioc_create_bufs: if T::HAS_VIDIOC_CREATE_BUFS { + Some(Self::vidioc_create_bufs_callback) + } else { + None + }, + vidioc_prepare_buf: if T::HAS_VIDIOC_PREPARE_BUF { + Some(Self::vidioc_prepare_buf_callback) + } else { + None + }, + vidioc_overlay: None, + vidioc_g_fbuf: None, + vidioc_s_fbuf: None, + vidioc_streamon: if T::HAS_VIDIOC_STREAMON { + Some(Self::vidioc_streamon_callback) + } else { + None + }, + vidioc_streamoff: if T::HAS_VIDIOC_STREAMOFF { + Some(Self::vidioc_streamoff_callback) + } else { + None + }, + vidioc_g_std: None, + vidioc_s_std: None, + vidioc_querystd: None, + vidioc_enum_input: if T::HAS_VIDIOC_ENUM_INPUT { + Some(Self::vidioc_enum_input_callback) + } else { + None + }, + vidioc_g_input: if T::HAS_VIDIOC_G_INPUT { + Some(Self::vidioc_g_input_callback) + } else { + None + }, + vidioc_s_input: if T::HAS_VIDIOC_S_INPUT { + Some(Self::vidioc_s_input_callback) + } else { + None + }, + vidioc_enum_output: None, + vidioc_g_output: None, + vidioc_s_output: None, + vidioc_queryctrl: None, + vidioc_query_ext_ctrl: None, + vidioc_g_ctrl: None, + vidioc_s_ctrl: None, + vidioc_g_ext_ctrls: None, + vidioc_s_ext_ctrls: None, + vidioc_try_ext_ctrls: None, + vidioc_querymenu: None, + vidioc_enumaudio: None, + vidioc_g_audio: None, + vidioc_s_audio: None, + vidioc_enumaudout: None, + vidioc_g_audout: None, + vidioc_s_audout: None, + vidioc_g_modulator: None, + vidioc_s_modulator: None, + vidioc_g_pixelaspect: None, + vidioc_g_selection: None, + vidioc_s_selection: None, + vidioc_g_jpegcomp: None, + vidioc_s_jpegcomp: None, + vidioc_g_enc_index: None, + vidioc_encoder_cmd: None, + vidioc_try_encoder_cmd: None, + vidioc_decoder_cmd: None, + vidioc_try_decoder_cmd: None, + vidioc_g_parm: None, + vidioc_s_parm: None, + vidioc_g_tuner: None, + vidioc_s_tuner: None, + vidioc_g_frequency: None, + vidioc_s_frequency: None, + vidioc_enum_freq_bands: None, + vidioc_g_sliced_vbi_cap: None, + vidioc_log_status: None, + vidioc_s_hw_freq_seek: None, + vidioc_enum_framesizes: if T::HAS_VIDIOC_ENUM_FRAMESIZES { + Some(Self::vidioc_enum_framesizes) + } else { + None + }, + vidioc_enum_frameintervals: None, + vidioc_s_dv_timings: None, + vidioc_g_dv_timings: None, + vidioc_query_dv_timings: None, + vidioc_enum_dv_timings: None, + vidioc_dv_timings_cap: None, + vidioc_g_edid: None, + vidioc_s_edid: None, + vidioc_subscribe_event: None, + vidioc_unsubscribe_event: None, + vidioc_default: None, + }; + + pub(crate) 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 77864640f19e..c60083266202 100644 --- a/rust/kernel/media/v4l2/mod.rs +++ b/rust/kernel/media/v4l2/mod.rs @@ -3,8 +3,11 @@ //! Abstractions for include/media/v4l2-*.h pub mod capabilities; +pub mod dev; +pub mod device; pub mod enums; pub mod format; pub mod framesize; pub mod inputs; +pub mod ioctls; pub mod mmap; From patchwork Thu Apr 6 21:56:15 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Almeida X-Patchwork-Id: 13204273 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 142F2C77B73 for ; Thu, 6 Apr 2023 21:58:41 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232471AbjDFV6N (ORCPT ); Thu, 6 Apr 2023 17:58:13 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:44954 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S239167AbjDFV5W (ORCPT ); Thu, 6 Apr 2023 17:57:22 -0400 Received: from madras.collabora.co.uk (madras.collabora.co.uk [46.235.227.172]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A1863AF3A; Thu, 6 Apr 2023 14:56:46 -0700 (PDT) Received: from localhost.localdomain (unknown [IPv6:2804:14d:72b4:8284:32a8:8167:f815:2895]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) (Authenticated sender: dwlsalmeida) by madras.collabora.co.uk (Postfix) with ESMTPSA id EF76666031F2; Thu, 6 Apr 2023 22:56:42 +0100 (BST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com; s=mail; t=1680818205; bh=GNp7AViZIGE3DK9wiwiWUEJC1nCGATb7ALyHIa7a9B0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=bpIGYfCsaJRyOC8eRp5YeEPoPe/m0+JQp4xIuVbCmbz3cDkoATTSuzVyjASAmxw98 NhD1w/cu5YVIII8bjiu3pmrDKGfe0pKgvNZ/Mo7/M7HspPpC8AIBkOlZNTM6tqcpUj b+rdSCZnQ7Lo/N8l/5u7ZxMWt8/qU6N6KyhuFvtBbpg3OUbeiMZxdVDLjuqDe0BYP5 +Mx5LEzM2NfCuQ64U3S79QvlaJtifHrRunS6UyMoYftG6MqZsksV29HIKm2tAbbyx5 kp0LOD3NNbtQ8vGmZAMQBXTKdvlWZogvvcUV9jlGMOoDjqpoabCbvYSWr9Imp05SOR /FZM5IbtatfvQ== From: Daniel Almeida To: wedsonaf@gmail.com, ojeda@kernel.org, mchehab@kernel.org, hverkuil@xs4all.nl Cc: Daniel Almeida , rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, linux-media@vger.kernel.org, kernel@collabora.com Subject: [PATCH 6/6] rust: media: add v4l2 rust sample Date: Thu, 6 Apr 2023 18:56:15 -0300 Message-Id: <20230406215615.122099-7-daniel.almeida@collabora.com> X-Mailer: git-send-email 2.40.0 In-Reply-To: <20230406215615.122099-1-daniel.almeida@collabora.com> References: <20230406215615.122099-1-daniel.almeida@collabora.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org 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 --- 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>>>, + _pdev: Pin>, +} + +impl v4l2::dev::Driver for Driver { + type DriverData = Arc; +} + +pub(crate) struct Registrations { + video: Pin>>, + v4l2: Pin>, +} + +type DeviceData = kernel::device::Data; + +pub(crate) struct V4l2Data { + pub(crate) _device: Arc, + _vb2_queue: Mutex>, +} + +#[vtable] +impl videobuf2::core::QueueOperations for Driver { + type DriverSpecificData = Vb2QueueData; + + type PrivateData = Box>; + + 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; + + kernel::define_of_id_table! {(), [ + (kernel::of::DeviceId::Compatible(b"v4l2"), None), + ]} + + fn probe( + pdev: &mut platform::Device, + _: core::option::Option<&Self::IdInfo>, + ) -> Result { + 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::::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: ×tamp_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::::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::::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; + + 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); + +// 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 { + // Register as platform driver + let reg: Pin>>> = + 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", +}