Linux Integrity Measurement development
 help / color / mirror / Atom feed
* [PATCH v2 1/3] integrity: Make arch_ima_get_secureboot integrity-wide
From: Coiby Xu @ 2026-02-03  4:14 UTC (permalink / raw)
  To: linux-integrity
  Cc: Heiko Carstens, Alexander Egorenkov, Ard Biesheuvel, Dave Hansen,
	Mimi Zohar, Roberto Sassu, Madhavan Srinivasan, Michael Ellerman,
	Nicholas Piggin, Christophe Leroy (CS GROUP), Vasily Gorbik,
	Alexander Gordeev, Christian Borntraeger, Sven Schnelle,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	maintainer:X86 ARCHITECTURE (32-BIT AND 64-BIT), H. Peter Anvin,
	Dmitry Kasatkin, Eric Snowberg, Paul Moore, James Morris,
	Serge E. Hallyn, Jarkko Sakkinen, open list,
	open list:LINUX FOR POWERPC (32-BIT AND 64-BIT),
	open list:S390 ARCHITECTURE,
	open list:EXTENSIBLE FIRMWARE INTERFACE (EFI),
	open list:SECURITY SUBSYSTEM, open list:KEYS/KEYRINGS_INTEGRITY
In-Reply-To: <20260203041434.872784-1-coxu@redhat.com>

EVM and other LSMs need the ability to query the secure boot status of
the system, without directly calling the IMA arch_ima_get_secureboot
function. Refactor the secure boot status check into a general function
named arch_get_secureboot.

Reported-and-suggested-by: Mimi Zohar <zohar@linux.ibm.com>
Suggested-by: Roberto Sassu <roberto.sassu@huawei.com>
Signed-off-by: Coiby Xu <coxu@redhat.com>
---
 MAINTAINERS                                   |  1 +
 arch/powerpc/kernel/ima_arch.c                |  5 --
 arch/powerpc/kernel/secure_boot.c             |  6 ++
 arch/s390/kernel/ima_arch.c                   |  6 --
 arch/s390/kernel/ipl.c                        |  5 ++
 arch/x86/include/asm/efi.h                    |  4 +-
 arch/x86/platform/efi/efi.c                   |  2 +-
 include/linux/ima.h                           |  7 +--
 include/linux/secure_boot.h                   | 19 +++++++
 security/integrity/Makefile                   |  3 +-
 security/integrity/efi_secureboot.c           | 56 +++++++++++++++++++
 security/integrity/ima/ima_appraise.c         |  2 +-
 security/integrity/ima/ima_efi.c              | 48 +---------------
 security/integrity/ima/ima_main.c             |  4 +-
 security/integrity/integrity.h                |  1 +
 security/integrity/platform_certs/load_uefi.c |  2 +-
 security/integrity/secure_boot.c              | 16 ++++++
 17 files changed, 117 insertions(+), 70 deletions(-)
 create mode 100644 include/linux/secure_boot.h
 create mode 100644 security/integrity/efi_secureboot.c
 create mode 100644 security/integrity/secure_boot.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 67db88b04537..1f963a621a99 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12519,6 +12519,7 @@ R:	Eric Snowberg <eric.snowberg@oracle.com>
 L:	linux-integrity@vger.kernel.org
 S:	Supported
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git
+F:	include/linux/secure_boot.h
 F:	security/integrity/
 F:	security/integrity/ima/
 
diff --git a/arch/powerpc/kernel/ima_arch.c b/arch/powerpc/kernel/ima_arch.c
index b7029beed847..0d8892a03526 100644
--- a/arch/powerpc/kernel/ima_arch.c
+++ b/arch/powerpc/kernel/ima_arch.c
@@ -7,11 +7,6 @@
 #include <linux/ima.h>
 #include <asm/secure_boot.h>
 
-bool arch_ima_get_secureboot(void)
-{
-	return is_ppc_secureboot_enabled();
-}
-
 /*
  * The "secure_rules" are enabled only on "secureboot" enabled systems.
  * These rules verify the file signatures against known good values.
diff --git a/arch/powerpc/kernel/secure_boot.c b/arch/powerpc/kernel/secure_boot.c
index 3a28795b4ed8..28436c1599e0 100644
--- a/arch/powerpc/kernel/secure_boot.c
+++ b/arch/powerpc/kernel/secure_boot.c
@@ -5,6 +5,7 @@
  */
 #include <linux/types.h>
 #include <linux/of.h>
+#include <linux/secure_boot.h>
 #include <linux/string_choices.h>
 #include <asm/secure_boot.h>
 
@@ -44,6 +45,11 @@ bool is_ppc_secureboot_enabled(void)
 	return enabled;
 }
 
+bool arch_get_secureboot(void)
+{
+	return is_ppc_secureboot_enabled();
+}
+
 bool is_ppc_trustedboot_enabled(void)
 {
 	struct device_node *node;
diff --git a/arch/s390/kernel/ima_arch.c b/arch/s390/kernel/ima_arch.c
index f3c3e6e1c5d3..6ccbe34ce408 100644
--- a/arch/s390/kernel/ima_arch.c
+++ b/arch/s390/kernel/ima_arch.c
@@ -1,12 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 
 #include <linux/ima.h>
-#include <asm/boot_data.h>
-
-bool arch_ima_get_secureboot(void)
-{
-	return ipl_secure_flag;
-}
 
 const char * const *arch_get_ima_policy(void)
 {
diff --git a/arch/s390/kernel/ipl.c b/arch/s390/kernel/ipl.c
index dcdc7e274848..781deb588557 100644
--- a/arch/s390/kernel/ipl.c
+++ b/arch/s390/kernel/ipl.c
@@ -2504,6 +2504,11 @@ void *ipl_report_finish(struct ipl_report *report)
 	return buf;
 }
 
+bool arch_get_secureboot(void)
+{
+	return ipl_secure_flag;
+}
+
 int ipl_report_free(struct ipl_report *report)
 {
 	struct ipl_report_component *comp, *ncomp;
diff --git a/arch/x86/include/asm/efi.h b/arch/x86/include/asm/efi.h
index f227a70ac91f..ee382b56dd7b 100644
--- a/arch/x86/include/asm/efi.h
+++ b/arch/x86/include/asm/efi.h
@@ -401,9 +401,9 @@ extern int __init efi_memmap_split_count(efi_memory_desc_t *md,
 extern void __init efi_memmap_insert(struct efi_memory_map *old_memmap,
 				     void *buf, struct efi_mem_range *mem);
 
-extern enum efi_secureboot_mode __x86_ima_efi_boot_mode(void);
+enum efi_secureboot_mode __x86_efi_boot_mode(void);
 
-#define arch_ima_efi_boot_mode	__x86_ima_efi_boot_mode()
+#define arch_efi_boot_mode __x86_efi_boot_mode()
 
 #ifdef CONFIG_EFI_RUNTIME_MAP
 int efi_get_runtime_map_size(void);
diff --git a/arch/x86/platform/efi/efi.c b/arch/x86/platform/efi/efi.c
index 463b784499a8..d8b25ae7af1e 100644
--- a/arch/x86/platform/efi/efi.c
+++ b/arch/x86/platform/efi/efi.c
@@ -921,7 +921,7 @@ umode_t efi_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n)
 	return attr->mode;
 }
 
-enum efi_secureboot_mode __x86_ima_efi_boot_mode(void)
+enum efi_secureboot_mode __x86_efi_boot_mode(void)
 {
 	return boot_params.secure_boot;
 }
diff --git a/include/linux/ima.h b/include/linux/ima.h
index 8e29cb4e6a01..b3927b795a60 100644
--- a/include/linux/ima.h
+++ b/include/linux/ima.h
@@ -11,6 +11,7 @@
 #include <linux/fs.h>
 #include <linux/security.h>
 #include <linux/kexec.h>
+#include <linux/secure_boot.h>
 #include <crypto/hash_info.h>
 struct linux_binprm;
 
@@ -72,14 +73,8 @@ int __init ima_get_kexec_buffer(void **addr, size_t *size);
 #endif
 
 #ifdef CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT
-extern bool arch_ima_get_secureboot(void);
 extern const char * const *arch_get_ima_policy(void);
 #else
-static inline bool arch_ima_get_secureboot(void)
-{
-	return false;
-}
-
 static inline const char * const *arch_get_ima_policy(void)
 {
 	return NULL;
diff --git a/include/linux/secure_boot.h b/include/linux/secure_boot.h
new file mode 100644
index 000000000000..3ded3f03655c
--- /dev/null
+++ b/include/linux/secure_boot.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2026 Red Hat, Inc. All Rights Reserved.
+ *
+ * Author: Coiby Xu <coxu@redhat.com>
+ */
+
+#ifndef _LINUX_SECURE_BOOT_H
+#define _LINUX_SECURE_BOOT_H
+
+#include <linux/types.h>
+
+/*
+ * Returns true if the platform secure boot is enabled.
+ * Returns false if disabled or not supported.
+ */
+bool arch_get_secureboot(void);
+
+#endif /* _LINUX_SECURE_BOOT_H */
diff --git a/security/integrity/Makefile b/security/integrity/Makefile
index 92b63039c654..548665e2b702 100644
--- a/security/integrity/Makefile
+++ b/security/integrity/Makefile
@@ -5,7 +5,7 @@
 
 obj-$(CONFIG_INTEGRITY) += integrity.o
 
-integrity-y := iint.o
+integrity-y := iint.o secure_boot.o
 integrity-$(CONFIG_INTEGRITY_AUDIT) += integrity_audit.o
 integrity-$(CONFIG_INTEGRITY_SIGNATURE) += digsig.o
 integrity-$(CONFIG_INTEGRITY_ASYMMETRIC_KEYS) += digsig_asymmetric.o
@@ -18,6 +18,7 @@ integrity-$(CONFIG_LOAD_IPL_KEYS) += platform_certs/load_ipl_s390.o
 integrity-$(CONFIG_LOAD_PPC_KEYS) += platform_certs/efi_parser.o \
                                      platform_certs/load_powerpc.o \
                                      platform_certs/keyring_handler.o
+integrity-$(CONFIG_EFI) += efi_secureboot.o
 # The relative order of the 'ima' and 'evm' LSMs depends on the order below.
 obj-$(CONFIG_IMA)			+= ima/
 obj-$(CONFIG_EVM)			+= evm/
diff --git a/security/integrity/efi_secureboot.c b/security/integrity/efi_secureboot.c
new file mode 100644
index 000000000000..bfd4260a83a3
--- /dev/null
+++ b/security/integrity/efi_secureboot.c
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-1.0+
+/*
+ * Copyright (C) 2018 IBM Corporation
+ */
+#include <linux/efi.h>
+#include <linux/secure_boot.h>
+#include <asm/efi.h>
+
+#ifndef arch_efi_boot_mode
+#define arch_efi_boot_mode efi_secureboot_mode_unset
+#endif
+
+static enum efi_secureboot_mode get_sb_mode(void)
+{
+	enum efi_secureboot_mode mode;
+
+	if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE)) {
+		pr_info("integrity: secureboot mode unknown, no efi\n");
+		return efi_secureboot_mode_unknown;
+	}
+
+	mode = efi_get_secureboot_mode(efi.get_variable);
+	if (mode == efi_secureboot_mode_disabled)
+		pr_info("integrity: secureboot mode disabled\n");
+	else if (mode == efi_secureboot_mode_unknown)
+		pr_info("integrity: secureboot mode unknown\n");
+	else
+		pr_info("integrity: secureboot mode enabled\n");
+	return mode;
+}
+
+/*
+ * Query secure boot status
+ *
+ * Note don't call this function too early e.g. in __setup hook otherwise the
+ * kernel may hang when calling efi_get_secureboot_mode.
+ *
+ */
+bool arch_get_secureboot(void)
+{
+	static enum efi_secureboot_mode sb_mode;
+	static bool initialized;
+
+	if (!initialized && efi_enabled(EFI_BOOT)) {
+		sb_mode = arch_efi_boot_mode;
+
+		if (sb_mode == efi_secureboot_mode_unset)
+			sb_mode = get_sb_mode();
+		initialized = true;
+	}
+
+	if (sb_mode == efi_secureboot_mode_enabled)
+		return true;
+	else
+		return false;
+}
diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c
index 5149ff4fd50d..9737bf76ce17 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -27,7 +27,7 @@ core_param(ima_appraise, ima_appraise_cmdline_default, charp, 0);
 void __init ima_appraise_parse_cmdline(void)
 {
 	const char *str = ima_appraise_cmdline_default;
-	bool sb_state = arch_ima_get_secureboot();
+	bool sb_state = arch_get_secureboot();
 	int appraisal_state = ima_appraise;
 
 	if (!str)
diff --git a/security/integrity/ima/ima_efi.c b/security/integrity/ima/ima_efi.c
index 138029bfcce1..27521d665d33 100644
--- a/security/integrity/ima/ima_efi.c
+++ b/security/integrity/ima/ima_efi.c
@@ -2,52 +2,9 @@
 /*
  * Copyright (C) 2018 IBM Corporation
  */
-#include <linux/efi.h>
 #include <linux/module.h>
 #include <linux/ima.h>
-#include <asm/efi.h>
-
-#ifndef arch_ima_efi_boot_mode
-#define arch_ima_efi_boot_mode efi_secureboot_mode_unset
-#endif
-
-static enum efi_secureboot_mode get_sb_mode(void)
-{
-	enum efi_secureboot_mode mode;
-
-	if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE)) {
-		pr_info("ima: secureboot mode unknown, no efi\n");
-		return efi_secureboot_mode_unknown;
-	}
-
-	mode = efi_get_secureboot_mode(efi.get_variable);
-	if (mode == efi_secureboot_mode_disabled)
-		pr_info("ima: secureboot mode disabled\n");
-	else if (mode == efi_secureboot_mode_unknown)
-		pr_info("ima: secureboot mode unknown\n");
-	else
-		pr_info("ima: secureboot mode enabled\n");
-	return mode;
-}
-
-bool arch_ima_get_secureboot(void)
-{
-	static enum efi_secureboot_mode sb_mode;
-	static bool initialized;
-
-	if (!initialized && efi_enabled(EFI_BOOT)) {
-		sb_mode = arch_ima_efi_boot_mode;
-
-		if (sb_mode == efi_secureboot_mode_unset)
-			sb_mode = get_sb_mode();
-		initialized = true;
-	}
-
-	if (sb_mode == efi_secureboot_mode_enabled)
-		return true;
-	else
-		return false;
-}
+#include <linux/secure_boot.h>
 
 /* secureboot arch rules */
 static const char * const sb_arch_rules[] = {
@@ -67,7 +24,8 @@ static const char * const sb_arch_rules[] = {
 
 const char * const *arch_get_ima_policy(void)
 {
-	if (IS_ENABLED(CONFIG_IMA_ARCH_POLICY) && arch_ima_get_secureboot()) {
+	if (IS_ENABLED(CONFIG_IMA_ARCH_POLICY) &&
+	    arch_get_secureboot()) {
 		if (IS_ENABLED(CONFIG_MODULE_SIG))
 			set_module_sig_enforced();
 		if (IS_ENABLED(CONFIG_KEXEC_SIG))
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index 5770cf691912..6d093ac82a45 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -949,8 +949,8 @@ static int ima_load_data(enum kernel_load_data_id id, bool contents)
 
 	switch (id) {
 	case LOADING_KEXEC_IMAGE:
-		if (IS_ENABLED(CONFIG_KEXEC_SIG)
-		    && arch_ima_get_secureboot()) {
+		if (IS_ENABLED(CONFIG_KEXEC_SIG) &&
+		    arch_get_secureboot()) {
 			pr_err("impossible to appraise a kernel image without a file descriptor; try using kexec_file_load syscall.\n");
 			return -EACCES;
 		}
diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h
index 7b388b66cf80..4636629533af 100644
--- a/security/integrity/integrity.h
+++ b/security/integrity/integrity.h
@@ -14,6 +14,7 @@
 
 #include <linux/types.h>
 #include <linux/integrity.h>
+#include <linux/secure_boot.h>
 #include <crypto/sha1.h>
 #include <crypto/hash.h>
 #include <linux/key.h>
diff --git a/security/integrity/platform_certs/load_uefi.c b/security/integrity/platform_certs/load_uefi.c
index d1fdd113450a..c0d6948446c3 100644
--- a/security/integrity/platform_certs/load_uefi.c
+++ b/security/integrity/platform_certs/load_uefi.c
@@ -212,7 +212,7 @@ static int __init load_uefi_certs(void)
 	}
 
 	/* the MOK/MOKx can not be trusted when secure boot is disabled */
-	if (!arch_ima_get_secureboot())
+	if (!arch_get_secureboot())
 		return 0;
 
 	mokx = get_cert_list(L"MokListXRT", &mok_var, &mokxsize, &status);
diff --git a/security/integrity/secure_boot.c b/security/integrity/secure_boot.c
new file mode 100644
index 000000000000..fc2693c286f8
--- /dev/null
+++ b/security/integrity/secure_boot.c
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Red Hat, Inc. All Rights Reserved.
+ *
+ * Author: Coiby Xu <coxu@redhat.com>
+ */
+#include <linux/secure_boot.h>
+
+/*
+ * Default weak implementation.
+ * Architectures that support secure boot must override this.
+ */
+__weak bool arch_get_secureboot(void)
+{
+	return false;
+}
-- 
2.52.0


^ permalink raw reply related

* [PATCH v2 0/3] Make detecting the secure boot status integrity-wide
From: Coiby Xu @ 2026-02-03  4:14 UTC (permalink / raw)
  To: linux-integrity
  Cc: Heiko Carstens, Alexander Egorenkov, Ard Biesheuvel, Dave Hansen

EVM and other LSMs need the ability to query the secure boot status of
the system, without directly calling the IMA arch_ima_get_secureboot
function. Make arch_ima_get_secureboot integrity-wide.

v2:
 - drop CONFIG_INTEGRITY_SECURE_BOOT Kconfig option since it 
   "imply INTEGRITY_SECURE_BOOT" is anti-pattern as pointed out by
   Ard Biesheuvel

Coiby Xu (3):
  integrity: Make arch_ima_get_secureboot integrity-wide
  evm: Don't enable fix mode when secure boot is enabled
  s390: Drop unnecessary CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT

 MAINTAINERS                                   |  1 +
 arch/powerpc/kernel/ima_arch.c                |  5 --
 arch/powerpc/kernel/secure_boot.c             |  6 ++
 arch/s390/Kconfig                             |  1 -
 arch/s390/kernel/Makefile                     |  1 -
 arch/s390/kernel/ima_arch.c                   | 14 -----
 arch/s390/kernel/ipl.c                        |  5 ++
 arch/x86/include/asm/efi.h                    |  4 +-
 arch/x86/platform/efi/efi.c                   |  2 +-
 include/linux/ima.h                           |  7 +--
 include/linux/secure_boot.h                   | 19 +++++++
 security/integrity/Makefile                   |  3 +-
 security/integrity/efi_secureboot.c           | 56 +++++++++++++++++++
 security/integrity/evm/evm_main.c             | 24 +++++---
 security/integrity/ima/ima_appraise.c         |  2 +-
 security/integrity/ima/ima_efi.c              | 48 +---------------
 security/integrity/ima/ima_main.c             |  4 +-
 security/integrity/integrity.h                |  1 +
 security/integrity/platform_certs/load_uefi.c |  2 +-
 security/integrity/secure_boot.c              | 16 ++++++
 20 files changed, 134 insertions(+), 87 deletions(-)
 delete mode 100644 arch/s390/kernel/ima_arch.c
 create mode 100644 include/linux/secure_boot.h
 create mode 100644 security/integrity/efi_secureboot.c
 create mode 100644 security/integrity/secure_boot.c


base-commit: 8dfce8991b95d8625d0a1d2896e42f93b9d7f68d
-- 
2.52.0


^ permalink raw reply

* Re: [RFC PATCH v2] media: Virtual camera driver
From: Jarkko Sakkinen @ 2026-02-03  1:36 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: linux-media, jani.nikula, anisse, oleksandr, linux-integrity,
	Mauro Carvalho Chehab, Hans Verkuil, Laurent Pinchart,
	Jacopo Mondi, Ricardo Ribalda, open list
In-Reply-To: <aYE84i2GT5ntqZsO@kernel.org>

On Tue, Feb 03, 2026 at 02:10:15AM +0200, Jarkko Sakkinen wrote:
> On Tue, Feb 03, 2026 at 12:50:06AM +0200, Sakari Ailus wrote:
> > Hi Jarkko,
> > 
> > On Mon, Feb 02, 2026 at 10:44:21PM +0200, Jarkko Sakkinen wrote:
> > > Already a quick Google survey backs strongly that OOT drivers (e.g.,
> > > v4l2loopback) are the defacto solution for streaming phone cameras in
> > > video conference calls, which puts confidential discussions at risk.
> > 
> > As I think it was pointed out in review comments for v1, the reason behind
> > using v4l2loopback is the use of a downstream driver, which itself is a
> > source of a security risk. If I understand correctly, supporting this
> > (proprietary/downstream vendor drivers) would be the main use case this
> > driver serves? Should this downstream driver be upstreamed to alleviate the
> > security risks, the need for v4l2loopback or similar drivers presumably
> > disappears.
> 
> My goal is not to proactively support proprietary drivers, and I don't
> know how to measure such incentive or risk, when it comes to video
> drivers.
> 
> And besides there is e.g. FUSE.
> 
> > 
> > Another of the downsides of such proprietary/downstream solutions is they
> > can never be properly integrated into the Linux ecosystem so functionality
> > will remain spotty (limited to specific systems and specific releases of
> > specific distributions) at best.
> > 
> > In other words, this driver appears to be orthogonal to solving either of
> > the above two problems the proprietary/downstream solutions have.
> > 
> > From the Open Source libcamera based camera software stack point of view
> > there doesn't seem to be a need for v4l2loopback or another similar driver.
> > The two main reasons for this is that (1) there's no need for glueing
> > something separate together like this and (2) V4L2 isn't a great
> > application interface for cameras -- use libcamera or Pipewire instead.
> 
> While I get this argument isolated, it does not match the observed
> reality, and does not provide tools to address the core issue. I
> will be in my grave before I've fixed the world like you are
> suggesting :-)
> 
> Like, first off, where would I use libcamera or Pipewire? There's
> no well-defined target other than kernel in this problem.
> 
> > 
> > > 
> > > It can be also claimed that there's enough OOT usage in the wild that
> > > possible security bugs could be considered as potential zerodays for the
> > > benefit of malicious actors.
> > > 
> > > The situation has been stagnated for however many years, which is
> > > unsastainable situation, and it further factors potential security
> > > risks. Therefore, a driver is needed to address the popular use case.
> > > 
> > > vcam is a DMA-BUF backed virtual camera driver capable of creating video
> > > capture devices to which data can be streamed through /dev/vcam after
> > > calling VCAM_IOC_CREATE. Frames are pushed with VCAM_IOC_QUEUE and recycled
> > > with VCAM_IOC_DEQUEUE. Zero-copy semantics are supported for shared DMA-BUF
> > > between capture and output.
> > > 
> > > This enables efficient implementation of software, which can manage network
> > > video streams from phone cameras, and map those streams to video devices.
> > 
> > I'd really try to avoid involving V4L2 in-kernel implementation when the
> > source of the video is network. V4L2 is meant to be used (when it comes to
> > video) for interfacing video related hardware such as cameras, ISPs and
> > codecs. There are limited number of video output related devices, too, but
> > network is something quite different from these.
> 
> I'd look at the usage patterns in the field too. It is pretty obvious
> that there is a significant gap what users want and expect when it
> comes to this debate.

As for the patch itself, it is RFC i.e., not request for immediate
merge. I sent v2 quickly primarily to address the motivation part
properly. I'll phase this down a bit, and rework on issues in the uAPI I
observed (and remarked in a response to this patch) etc., and generally
give people some time to digest.

BR, Jarkko

^ permalink raw reply

* [GIT PULL] TPM DEVICE DRIVER: tpmdd-next-7.0-rc1
From: Jarkko Sakkinen @ 2026-02-03  0:46 UTC (permalink / raw)
  To: Linus Torvalds
  Cc: Peter Huewe, Jason Gunthorpe, David Howells, keyrings,
	linux-integrity, linux-kernel

The following changes since commit dee65f79364c18033cabdf0728c7e7025405cf40:

  Merge tag 'lsm-pr-20260202' of git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/lsm (2026-02-02 09:48:23 -0800)

are available in the Git repository at:

  git://git.kernel.org/pub/scm/linux/kernel/git/jarkko/linux-tpmdd.git tags/tpmdd-next-7.0-rc1

for you to fetch changes up to 3e91b44c93ad2871f89fc2a98c5e4fe6ca5db3d9:

  tpm: st33zp24: Fix missing cleanup on get_burstcount() error (2026-02-03 02:36:32 +0200)

----------------------------------------------------------------
Hi,

Here are TPM driver updates for Linux v7.0.

BR, Jarkko

----------------------------------------------------------------
Alper Ak (2):
      tpm: tpm_i2c_infineon: Fix locality leak on get_burstcount() failure
      tpm: st33zp24: Fix missing cleanup on get_burstcount() error

 drivers/char/tpm/st33zp24/st33zp24.c | 6 ++++--
 drivers/char/tpm/tpm_i2c_infineon.c  | 6 ++++--
 2 files changed, 8 insertions(+), 4 deletions(-)

^ permalink raw reply

* Re: [RFC PATCH v2] media: Virtual camera driver
From: Jarkko Sakkinen @ 2026-02-03  0:10 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: linux-media, jani.nikula, anisse, oleksandr, linux-integrity,
	Mauro Carvalho Chehab, Hans Verkuil, Laurent Pinchart,
	Jacopo Mondi, Ricardo Ribalda, open list
In-Reply-To: <aYEqHogDBqR1qGw3@kekkonen.localdomain>

On Tue, Feb 03, 2026 at 12:50:06AM +0200, Sakari Ailus wrote:
> Hi Jarkko,
> 
> On Mon, Feb 02, 2026 at 10:44:21PM +0200, Jarkko Sakkinen wrote:
> > Already a quick Google survey backs strongly that OOT drivers (e.g.,
> > v4l2loopback) are the defacto solution for streaming phone cameras in
> > video conference calls, which puts confidential discussions at risk.
> 
> As I think it was pointed out in review comments for v1, the reason behind
> using v4l2loopback is the use of a downstream driver, which itself is a
> source of a security risk. If I understand correctly, supporting this
> (proprietary/downstream vendor drivers) would be the main use case this
> driver serves? Should this downstream driver be upstreamed to alleviate the
> security risks, the need for v4l2loopback or similar drivers presumably
> disappears.

My goal is not to proactively support proprietary drivers, and I don't
know how to measure such incentive or risk, when it comes to video
drivers.

And besides there is e.g. FUSE.

> 
> Another of the downsides of such proprietary/downstream solutions is they
> can never be properly integrated into the Linux ecosystem so functionality
> will remain spotty (limited to specific systems and specific releases of
> specific distributions) at best.
> 
> In other words, this driver appears to be orthogonal to solving either of
> the above two problems the proprietary/downstream solutions have.
> 
> From the Open Source libcamera based camera software stack point of view
> there doesn't seem to be a need for v4l2loopback or another similar driver.
> The two main reasons for this is that (1) there's no need for glueing
> something separate together like this and (2) V4L2 isn't a great
> application interface for cameras -- use libcamera or Pipewire instead.

While I get this argument isolated, it does not match the observed
reality, and does not provide tools to address the core issue. I
will be in my grave before I've fixed the world like you are
suggesting :-)

Like, first off, where would I use libcamera or Pipewire? There's
no well-defined target other than kernel in this problem.

> 
> > 
> > It can be also claimed that there's enough OOT usage in the wild that
> > possible security bugs could be considered as potential zerodays for the
> > benefit of malicious actors.
> > 
> > The situation has been stagnated for however many years, which is
> > unsastainable situation, and it further factors potential security
> > risks. Therefore, a driver is needed to address the popular use case.
> > 
> > vcam is a DMA-BUF backed virtual camera driver capable of creating video
> > capture devices to which data can be streamed through /dev/vcam after
> > calling VCAM_IOC_CREATE. Frames are pushed with VCAM_IOC_QUEUE and recycled
> > with VCAM_IOC_DEQUEUE. Zero-copy semantics are supported for shared DMA-BUF
> > between capture and output.
> > 
> > This enables efficient implementation of software, which can manage network
> > video streams from phone cameras, and map those streams to video devices.
> 
> I'd really try to avoid involving V4L2 in-kernel implementation when the
> source of the video is network. V4L2 is meant to be used (when it comes to
> video) for interfacing video related hardware such as cameras, ISPs and
> codecs. There are limited number of video output related devices, too, but
> network is something quite different from these.

I'd look at the usage patterns in the field too. It is pretty obvious
that there is a significant gap what users want and expect when it
comes to this debate.

> 
> -- 
> Kind regards,
> 
> Sakari Ailus

BR, Jarkko

^ permalink raw reply

* Re: [RFC PATCH v2] media: Virtual camera driver
From: Sakari Ailus @ 2026-02-02 22:50 UTC (permalink / raw)
  To: Jarkko Sakkinen
  Cc: linux-media, jani.nikula, anisse, oleksandr, linux-integrity,
	Mauro Carvalho Chehab, Hans Verkuil, Laurent Pinchart,
	Jacopo Mondi, Ricardo Ribalda, open list
In-Reply-To: <20260202204425.2614054-1-jarkko@kernel.org>

Hi Jarkko,

On Mon, Feb 02, 2026 at 10:44:21PM +0200, Jarkko Sakkinen wrote:
> Already a quick Google survey backs strongly that OOT drivers (e.g.,
> v4l2loopback) are the defacto solution for streaming phone cameras in
> video conference calls, which puts confidential discussions at risk.

As I think it was pointed out in review comments for v1, the reason behind
using v4l2loopback is the use of a downstream driver, which itself is a
source of a security risk. If I understand correctly, supporting this
(proprietary/downstream vendor drivers) would be the main use case this
driver serves? Should this downstream driver be upstreamed to alleviate the
security risks, the need for v4l2loopback or similar drivers presumably
disappears.

Another of the downsides of such proprietary/downstream solutions is they
can never be properly integrated into the Linux ecosystem so functionality
will remain spotty (limited to specific systems and specific releases of
specific distributions) at best.

In other words, this driver appears to be orthogonal to solving either of
the above two problems the proprietary/downstream solutions have.

From the Open Source libcamera based camera software stack point of view
there doesn't seem to be a need for v4l2loopback or another similar driver.
The two main reasons for this is that (1) there's no need for glueing
something separate together like this and (2) V4L2 isn't a great
application interface for cameras -- use libcamera or Pipewire instead.

> 
> It can be also claimed that there's enough OOT usage in the wild that
> possible security bugs could be considered as potential zerodays for the
> benefit of malicious actors.
> 
> The situation has been stagnated for however many years, which is
> unsastainable situation, and it further factors potential security
> risks. Therefore, a driver is needed to address the popular use case.
> 
> vcam is a DMA-BUF backed virtual camera driver capable of creating video
> capture devices to which data can be streamed through /dev/vcam after
> calling VCAM_IOC_CREATE. Frames are pushed with VCAM_IOC_QUEUE and recycled
> with VCAM_IOC_DEQUEUE. Zero-copy semantics are supported for shared DMA-BUF
> between capture and output.
> 
> This enables efficient implementation of software, which can manage network
> video streams from phone cameras, and map those streams to video devices.

I'd really try to avoid involving V4L2 in-kernel implementation when the
source of the video is network. V4L2 is meant to be used (when it comes to
video) for interfacing video related hardware such as cameras, ISPs and
codecs. There are limited number of video output related devices, too, but
network is something quite different from these.

-- 
Kind regards,

Sakari Ailus

^ permalink raw reply

* Re: [RFC PATCH v2] media: Virtual camera driver
From: Jarkko Sakkinen @ 2026-02-02 21:28 UTC (permalink / raw)
  To: linux-media
  Cc: jani.nikula, anisse, oleksandr, linux-integrity,
	Mauro Carvalho Chehab, Hans Verkuil, Laurent Pinchart,
	Sakari Ailus, Jacopo Mondi, Ricardo Ribalda, open list
In-Reply-To: <20260202204425.2614054-1-jarkko@kernel.org>

On Mon, Feb 02, 2026 at 10:44:21PM +0200, Jarkko Sakkinen wrote:
> Already a quick Google survey backs strongly that OOT drivers (e.g.,
> v4l2loopback) are the defacto solution for streaming phone cameras in
> video conference calls, which puts confidential discussions at risk.
> 
> It can be also claimed that there's enough OOT usage in the wild that
> possible security bugs could be considered as potential zerodays for the
> benefit of malicious actors.
> 
> The situation has been stagnated for however many years, which is
> unsastainable situation, and it further factors potential security
> risks. Therefore, a driver is needed to address the popular use case.
> 
> vcam is a DMA-BUF backed virtual camera driver capable of creating video
> capture devices to which data can be streamed through /dev/vcam after
> calling VCAM_IOC_CREATE. Frames are pushed with VCAM_IOC_QUEUE and recycled
> with VCAM_IOC_DEQUEUE. Zero-copy semantics are supported for shared DMA-BUF
> between capture and output.
> 
> This enables efficient implementation of software, which can manage network
> video streams from phone cameras, and map those streams to video devices.
> 
> PipeWire or any other specific pick of userspace software cannot really
> address the issue at scale, as e.g., the use of v4l2loopback is both wide
> and scattered.
> 
> Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
> ---
> v2:
> - Added motivation based on feedback to v1.
> - Provide a list of allowed modes for /dev/videoX in VCAM_IOC_CREATE.
> - Merged VCAM_IOC_WAIT and VCAM_IOC_STATUS.
> - Return state with operation flags in VCAM_IOC_WAIT.
> - Test program: https://git.kernel.org/pub/scm/linux/kernel/git/jarkko/vcam-test.git
> ---
>  .../driver-api/media/drivers/index.rst        |    1 +
>  .../driver-api/media/drivers/vcam.rst         |   16 +
>  MAINTAINERS                                   |    8 +
>  drivers/media/Kconfig                         |   13 +
>  drivers/media/Makefile                        |    1 +
>  drivers/media/vcam.c                          | 1935 +++++++++++++++++
>  include/uapi/linux/vcam.h                     |  141 ++
>  7 files changed, 2115 insertions(+)
>  create mode 100644 Documentation/driver-api/media/drivers/vcam.rst
>  create mode 100644 drivers/media/vcam.c
>  create mode 100644 include/uapi/linux/vcam.h
> 
> diff --git a/Documentation/driver-api/media/drivers/index.rst b/Documentation/driver-api/media/drivers/index.rst
> index 7f6f3dcd5c90..211cafc9c070 100644
> --- a/Documentation/driver-api/media/drivers/index.rst
> +++ b/Documentation/driver-api/media/drivers/index.rst
> @@ -27,6 +27,7 @@ Video4Linux (V4L) drivers
>  	zoran
>  	ccs/ccs
>  	ipu6
> +	vcam
>  
>  
>  Digital TV drivers
> diff --git a/Documentation/driver-api/media/drivers/vcam.rst b/Documentation/driver-api/media/drivers/vcam.rst
> new file mode 100644
> index 000000000000..b5a23144ebee
> --- /dev/null
> +++ b/Documentation/driver-api/media/drivers/vcam.rst
> @@ -0,0 +1,16 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +===========================
> +vcam: Virtual Camera Driver
> +===========================
> +
> +Theory of Operation
> +-------------------
> +
> +.. kernel-doc:: drivers/media/vcam.c
> +   :doc: Theory of Operation
> +
> +Driver uAPI
> +-----------
> +
> +.. kernel-doc:: include/uapi/linux/vcam.h
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 6863d5fa07a1..b8444ff48716 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -27504,6 +27504,14 @@ S:	Maintained
>  F:	drivers/media/common/videobuf2/*
>  F:	include/media/videobuf2-*
>  
> +VCAM V4L2 DRIVER
> +M:	Jarkko Sakkinen <jarkko@kernel.org>
> +L:	linux-media@vger.kernel.org
> +S:	Maintained
> +T:	git git://git.kernel.org/pub/scm/linux/kernel/git/jarkko/linux-tpmdd.git
> +F:	drivers/media/vcam.c
> +F:	include/uapi/linux/vcam.h
> +
>  VIDTV VIRTUAL DIGITAL TV DRIVER
>  M:	Daniel W. S. Almeida <dwlsalmeida@gmail.com>
>  L:	linux-media@vger.kernel.org
> diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig
> index 6abc9302cd84..f2f4b2ec9135 100644
> --- a/drivers/media/Kconfig
> +++ b/drivers/media/Kconfig
> @@ -239,6 +239,19 @@ source "drivers/media/firewire/Kconfig"
>  # Common driver options
>  source "drivers/media/common/Kconfig"
>  
> +config VCAM
> +	tristate "V4L2 virtual camera"
> +	depends on VIDEO_DEV
> +	default m
> +	select VIDEOBUF2_VMALLOC
> +	help
> +	  Say Y here to enable a DMA-BUF backed virtual camera driver capable
> +	  of creating video capture devices to which data can be streamed
> +	  through /dev/vcam after calling VCAM_IOC_CREATE. Frames are pushed
> +	  with VCAM_IOC_QUEUE and recycled with VCAM_IOC_DEQUEUE.
> +
> +	  When in doubt, say N.
> +
>  endmenu
>  
>  #
> diff --git a/drivers/media/Makefile b/drivers/media/Makefile
> index 20fac24e4f0f..d539fecbe498 100644
> --- a/drivers/media/Makefile
> +++ b/drivers/media/Makefile
> @@ -32,3 +32,4 @@ obj-$(CONFIG_CEC_CORE) += cec/
>  obj-y += common/ platform/ pci/ usb/ mmc/ firewire/ spi/ test-drivers/
>  obj-$(CONFIG_VIDEO_DEV) += radio/
>  
> +obj-$(CONFIG_VCAM) += vcam.o
> diff --git a/drivers/media/vcam.c b/drivers/media/vcam.c
> new file mode 100644
> index 000000000000..787e2585e12c
> --- /dev/null
> +++ b/drivers/media/vcam.c
> @@ -0,0 +1,1935 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) Jarkko Sakkinen 2025-2026
> + *
> + * Derived originally from v4l2loopback driver but is essentially a rewrite.
> + */
> +
> +/**
> + * DOC: Theory of Operation
> + *
> + * The driver exposes /dev/vcam for creating virtual capture devices via
> + * %VCAM_IOC_CREATE. The ioctl registers a video capture node, stores the
> + * allowed capture modes provided by the caller, and associates output buffers
> + * described by &struct vcam_frame with DMA-BUF file descriptors supplied by
> + * the caller. This also keeps output buffers owned by the caller, and
> + * accounted from the calling process.
> + *
> + * Frames are pushed to the capture device by queueing output buffers using
> + * %VCAM_IOC_QUEUE, and recycling them with %VCAM_IOC_DEQUEUE. Queueing without
> + * dequeuing eventually exhausts the output queue and stalls the producer.
> + *
> + * If both buffers reference the same DMA-BUF, the driver performs a zero-copy
> + * transfer by propagating metadata. Otherwise, if both buffers are mappable,
> + * the payload is copied into the capture buffer. When neither zero-copy nor a
> + * CPU mapping is possible, the capture buffer completes with an error.
> + */
> +
> +#include <linux/cleanup.h>
> +#include <linux/bitops.h>
> +#include <linux/atomic.h>
> +#include <linux/ctype.h>
> +#include <linux/file.h>
> +#include <linux/fs.h>
> +#include <linux/limits.h>
> +#include <linux/device.h>
> +#include <linux/mm.h>
> +#include <linux/module.h>
> +#include <linux/miscdevice.h>
> +#include <linux/poll.h>
> +#include <linux/sched.h>
> +#include <linux/time.h>
> +#include <linux/math64.h>
> +#include <linux/minmax.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +#include <linux/sort.h>
> +#include <linux/spinlock.h>
> +#include <linux/sysfs.h>
> +#include <linux/videodev2.h>
> +#include <linux/wait.h>
> +#include <media/v4l2-common.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/videobuf2-v4l2.h>
> +#include <media/videobuf2-vmalloc.h>
> +#include <uapi/linux/vcam.h>
> +
> +#undef pr_fmt
> +#define pr_fmt(fmt) "vcam: " fmt
> +
> +MODULE_DESCRIPTION("V4L2 virtual camera driver");
> +MODULE_LICENSE("GPL");
> +
> +#define VCAM_CARD_LABEL_MAX sizeof_field(struct video_device, name)
> +#define VCAM_FPS_MIN 1
> +#define VCAM_FPS_MAX 1000
> +
> +#define VCAM_MIN_WIDTH 2
> +#define VCAM_MIN_HEIGHT 2
> +#define VCAM_MAX_WIDTH 8192
> +#define VCAM_MAX_HEIGHT 8192
> +#define VCAM_DEFAULT_WIDTH 640
> +#define VCAM_DEFAULT_HEIGHT 480
> +
> +#define VCAM_MAX_FORMATS 16
> +#define VCAM_MAX_MODES 64
> +#define VCAM_MIN_FRAMES 2
> +#define VCAM_MAX_FRAMES 32
> +
> +#define VCAM_STATUS_MASK (VCAM_STATUS_IDLE | VCAM_STATUS_STREAMING)
> +
> +enum vcam_flags {
> +	VCAM_FLAG_IS_OPEN = 0x01,
> +	VCAM_FLAG_CREATING = 0x02,
> +	VCAM_FLAG_READY = 0x04,
> +};
> +
> +struct vcam_buf {
> +	struct vb2_v4l2_buffer vb;
> +	struct list_head list;
> +	unsigned long flags;
> +};
> +
> +enum vcam_buf_flags {
> +	VCAM_BUF_FLAG_MAPPABLE = BIT(0),
> +};
> +
> +struct vcam {
> +	unsigned long flags;
> +	int device_nr;
> +	struct v4l2_device v4l2_dev;
> +	struct video_device *vdev;
> +	struct vb2_queue capture_queue;
> +	struct vb2_queue output_queue;
> +	struct v4l2_pix_format pix_format;
> +	struct vcam_mode *modes;
> +	u32 nr_modes;
> +	struct v4l2_captureparm capture;
> +	atomic_t sequence;
> +	struct list_head capture_list;
> +	struct list_head output_list;
> +	u64 status;
> +	wait_queue_head_t status_waitq;
> +	enum vb2_memory output_memory;
> +
> +	/* Protects status flags and wait queue updates. */
> +	spinlock_t status_lock;
> +
> +	/* Shared lock for vdev and VB2 queues. */
> +	struct mutex lock;
> +
> +	/* Protects capture_list and output_list. */
> +	spinlock_t frame_lock;
> +
> +	/*
> +	 * Maintains a shared reference between processes having either
> +	 * /dev/vcam or /dev/videoX open.
> +	 */
> +	struct kref ref;
> +};
> +
> +enum vcam_format_flags {
> +	VCAM_PLANAR = BIT(0),
> +	VCAM_COMPRESSED = BIT(1),
> +};
> +
> +struct vcam_format {
> +	int fourcc;
> +	int depth;
> +	int flags;
> +};
> +
> +const struct vcam_format vcam_formats[] = {
> +	{
> +		.fourcc = V4L2_PIX_FMT_YUYV,
> +		.depth = 16,
> +		.flags = 0,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_NV12,
> +		.depth = 12,
> +		.flags = VCAM_PLANAR,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_MJPEG,
> +		.depth = 32,
> +		.flags = VCAM_COMPRESSED,
> +	},
> +};
> +
> +#define VCAM_NR_FORMATS ARRAY_SIZE(vcam_formats)
> +
> +static const struct vcam_format *vcam_find_format(int fourcc)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < VCAM_NR_FORMATS; i++) {
> +		if (vcam_formats[i].fourcc == fourcc)
> +			return vcam_formats + i;
> +	}
> +
> +	return NULL;
> +}
> +
> +static void vcam_fmt_descr(char *dst, size_t dst_len, u32 format)
> +{
> +	snprintf(dst, dst_len, "[%c%c%c%c]", (format >> 0) & 0xFF,
> +		 (format >> 8) & 0xFF, (format >> 16) & 0xFF,
> +		 (format >> 24) & 0xFF);
> +}
> +
> +static void vcam_fourcc_str(char *dst, u32 format)
> +{
> +	dst[0] = (format >> 0) & 0xFF;
> +	dst[1] = (format >> 8) & 0xFF;
> +	dst[2] = (format >> 16) & 0xFF;
> +	dst[3] = (format >> 24) & 0xFF;
> +	dst[4] = '\0';
> +}
> +
> +static inline bool vcam_is_streaming(struct vcam *data)
> +{
> +	return vb2_is_streaming(&data->output_queue) ||
> +	       vb2_is_streaming(&data->capture_queue);
> +}
> +
> +static bool vcam_status_mask_ready(struct vcam *dev, u64 mask)
> +{
> +	unsigned long flags;
> +	bool ready;
> +
> +	spin_lock_irqsave(&dev->status_lock, flags);
> +	ready = (dev->status & mask) == mask;
> +	spin_unlock_irqrestore(&dev->status_lock, flags);
> +
> +	return ready;
> +}
> +
> +static void vcam_status_update_stream(struct vcam *dev, bool on)
> +{
> +	unsigned long flags;
> +	u64 old_flags;
> +	u64 new_flags;
> +
> +	spin_lock_irqsave(&dev->status_lock, flags);
> +	old_flags = dev->status;
> +	if (on) {
> +		dev->status &= ~VCAM_STATUS_IDLE;
> +		dev->status |= VCAM_STATUS_STREAMING;
> +	} else {
> +		dev->status &= ~VCAM_STATUS_STREAMING;
> +		dev->status |= VCAM_STATUS_IDLE;
> +	}
> +	new_flags = dev->status;
> +	spin_unlock_irqrestore(&dev->status_lock, flags);
> +
> +	if (new_flags != old_flags)
> +		wake_up_interruptible(&dev->status_waitq);
> +}
> +
> +static u64 vcam_status_read(struct vcam *dev)
> +{
> +	unsigned long flags;
> +	u64 flags_snapshot;
> +
> +	spin_lock_irqsave(&dev->status_lock, flags);
> +	flags_snapshot = dev->status;
> +	spin_unlock_irqrestore(&dev->status_lock, flags);
> +
> +	return flags_snapshot;
> +}
> +
> +static bool vcam_tpf_valid(const struct v4l2_fract *tpf)
> +{
> +	u64 min_den = (u64)tpf->numerator * VCAM_FPS_MIN;
> +	u64 max_den = (u64)tpf->numerator * VCAM_FPS_MAX;
> +
> +	if (!tpf->numerator || !tpf->denominator)
> +		return false;
> +	if ((u64)tpf->denominator < min_den)
> +		return false;
> +	if ((u64)tpf->denominator > max_den)
> +		return false;
> +
> +	return true;
> +}
> +
> +static bool vcam_pix_format_eq(const struct v4l2_pix_format *src,
> +			       const struct v4l2_pix_format *dest)
> +{
> +	return src->width == dest->width && src->height == dest->height &&
> +	       src->pixelformat == dest->pixelformat;
> +}
> +
> +static bool vcam_mode_has_pixelformat(const struct vcam *dev, u32 pixelformat)
> +{
> +	u32 i;
> +
> +	for (i = 0; i < dev->nr_modes; i++) {
> +		if (dev->modes[i].pixelformat == pixelformat)
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
> +static bool vcam_mode_pixelformat_seen(const struct vcam *dev, u32 index,
> +				       u32 pixelformat)
> +{
> +	u32 i;
> +
> +	for (i = 0; i < index; i++) {
> +		if (dev->modes[i].pixelformat == pixelformat)
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
> +static bool vcam_mode_framesize_seen(const struct vcam *dev, u32 index,
> +				     u32 pixelformat, u32 width, u32 height)
> +{
> +	u32 i;
> +
> +	for (i = 0; i < index; i++) {
> +		const struct vcam_mode *mode = &dev->modes[i];
> +
> +		if (mode->pixelformat == pixelformat && mode->width == width &&
> +		    mode->height == height)
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
> +static int vcam_mode_enum_format(const struct vcam *dev, u32 index,
> +				 u32 *pixelformat)
> +{
> +	u32 i;
> +	u32 match = 0;
> +
> +	for (i = 0; i < dev->nr_modes; i++) {
> +		u32 fmt = dev->modes[i].pixelformat;
> +
> +		if (vcam_mode_pixelformat_seen(dev, i, fmt))
> +			continue;
> +
> +		if (match++ == index) {
> +			*pixelformat = fmt;
> +			return 0;
> +		}
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int vcam_mode_enum_framesize(const struct vcam *dev, u32 pixelformat,
> +				    u32 index, u32 *width, u32 *height)
> +{
> +	u32 i;
> +	u32 match = 0;
> +
> +	for (i = 0; i < dev->nr_modes; i++) {
> +		const struct vcam_mode *mode = &dev->modes[i];
> +
> +		if (mode->pixelformat != pixelformat)
> +			continue;
> +		if (vcam_mode_framesize_seen(dev, i, pixelformat, mode->width,
> +					     mode->height))
> +			continue;
> +		if (match++ == index) {
> +			*width = mode->width;
> +			*height = mode->height;
> +			return 0;
> +		}
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static bool vcam_mode_has_framesize(const struct vcam *dev, u32 pixelformat,
> +				    u32 width, u32 height)
> +{
> +	u32 i;
> +
> +	for (i = 0; i < dev->nr_modes; i++) {
> +		const struct vcam_mode *mode = &dev->modes[i];
> +
> +		if (mode->pixelformat == pixelformat && mode->width == width &&
> +		    mode->height == height)
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
> +static bool vcam_mode_matches_pix(const struct vcam_mode *mode,
> +				  const struct v4l2_pix_format *pix)
> +{
> +	return mode->width == pix->width && mode->height == pix->height &&
> +	       mode->pixelformat == pix->pixelformat &&
> +	       mode->colorspace == pix->colorspace &&
> +	       mode->stride == pix->bytesperline;
> +}
> +
> +static int vcam_mode_cmp(const void *lhs, const void *rhs)
> +{
> +	const struct vcam_mode *a = lhs;
> +	const struct vcam_mode *b = rhs;
> +
> +	if (a->pixelformat != b->pixelformat)
> +		return (a->pixelformat > b->pixelformat) -
> +		       (a->pixelformat < b->pixelformat);
> +	if (a->width != b->width)
> +		return (a->width > b->width) - (a->width < b->width);
> +	if (a->height != b->height)
> +		return (a->height > b->height) - (a->height < b->height);
> +	if (a->colorspace != b->colorspace)
> +		return (a->colorspace > b->colorspace) -
> +		       (a->colorspace < b->colorspace);
> +	return (a->stride > b->stride) - (a->stride < b->stride);
> +}
> +
> +static bool vcam_mode_equal(const struct vcam_mode *a,
> +			    const struct vcam_mode *b)
> +{
> +	return a->width == b->width && a->height == b->height &&
> +	       a->pixelformat == b->pixelformat &&
> +	       a->colorspace == b->colorspace && a->stride == b->stride;
> +}
> +
> +static bool vcam_mode_allowed(const struct vcam *dev,
> +			      const struct v4l2_pix_format *pix)
> +{
> +	u32 i;
> +
> +	for (i = 0; i < dev->nr_modes; i++) {
> +		if (vcam_mode_matches_pix(&dev->modes[i], pix))
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
> +static int vcam_set_format(struct v4l2_format *fmt)
> +{
> +	struct v4l2_pix_format *pix = &fmt->fmt.pix;
> +	const struct vcam_format *format;
> +	u64 bytesperline;
> +	u64 sizeimage;
> +
> +	if (V4L2_TYPE_IS_MULTIPLANAR(fmt->type))
> +		return -EINVAL;
> +
> +	if (!pix->width)
> +		pix->width = VCAM_DEFAULT_WIDTH;
> +	if (!pix->height)
> +		pix->height = VCAM_DEFAULT_HEIGHT;
> +
> +	pix->width = clamp(pix->width, VCAM_MIN_WIDTH, VCAM_MAX_WIDTH);
> +	pix->height = clamp(pix->height, VCAM_MIN_HEIGHT, VCAM_MAX_HEIGHT);
> +
> +	format = vcam_find_format(pix->pixelformat);
> +	if (!format) {
> +		format = &vcam_formats[0];
> +		pix->pixelformat = format->fourcc;
> +	}
> +
> +	if (format->flags & VCAM_PLANAR) {
> +		pix->bytesperline = pix->width;
> +		sizeimage = ((u64)pix->width * pix->height * format->depth) >>
> +			    3;
> +	} else if (format->flags & VCAM_COMPRESSED) {
> +		pix->bytesperline = 0;
> +		sizeimage = ((u64)pix->width * pix->height * format->depth) >>
> +			    3;
> +	} else {
> +		bytesperline = ((u64)pix->width * format->depth) >> 3;
> +		if (bytesperline > U32_MAX)
> +			return -EOVERFLOW;
> +
> +		pix->bytesperline = bytesperline;
> +		sizeimage = (u64)pix->height * bytesperline;
> +	}
> +
> +	if (sizeimage > U32_MAX)
> +		return -EOVERFLOW;
> +
> +	pix->sizeimage = sizeimage;
> +
> +	if (pix->colorspace == V4L2_COLORSPACE_DEFAULT ||
> +	    pix->colorspace > V4L2_COLORSPACE_DCI_P3)
> +		pix->colorspace = V4L2_COLORSPACE_SRGB;
> +	if (pix->field == V4L2_FIELD_ANY)
> +		pix->field = V4L2_FIELD_NONE;
> +
> +	return 0;
> +}
> +
> +static int vcam_vidioc_querycap(struct file *file, void *priv,
> +				struct v4l2_capability *cap)
> +{
> +	__u32 capabilities = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE;
> +	struct vcam *dev = video_drvdata(file);
> +
> +	cap->device_caps = capabilities;
> +	cap->capabilities = capabilities | V4L2_CAP_DEVICE_CAPS;
> +
> +	strscpy(cap->driver, "vcam", sizeof(cap->driver));
> +	strscpy(cap->card, dev->vdev->name, sizeof(cap->card));
> +	snprintf(cap->bus_info, sizeof(cap->bus_info), "vcam:%d",
> +		 dev->device_nr);
> +
> +	return 0;
> +}
> +
> +static int vcam_enum_framesizes(struct vcam *dev, struct v4l2_frmsizeenum *argp)
> +{
> +	if (vcam_is_streaming(dev)) {
> +		if (argp->index)
> +			return -EINVAL;
> +		if (argp->pixel_format != dev->pix_format.pixelformat)
> +			return -EINVAL;
> +
> +		argp->type = V4L2_FRMSIZE_TYPE_DISCRETE;
> +
> +		argp->discrete.width = dev->pix_format.width;
> +		argp->discrete.height = dev->pix_format.height;
> +	} else {
> +		u32 width;
> +		u32 height;
> +
> +		if (!vcam_find_format(argp->pixel_format) ||
> +		    !vcam_mode_has_pixelformat(dev, argp->pixel_format))
> +			return -EINVAL;
> +
> +		if (vcam_mode_enum_framesize(dev, argp->pixel_format,
> +					     argp->index, &width, &height))
> +			return -EINVAL;
> +
> +		argp->type = V4L2_FRMSIZE_TYPE_DISCRETE;
> +		argp->discrete.width = width;
> +		argp->discrete.height = height;
> +	}
> +
> +	return 0;
> +}
> +
> +static int vcam_enum_frameintervals(struct vcam *dev,
> +				    struct v4l2_frmivalenum *argp)
> +{
> +	if (vcam_is_streaming(dev)) {
> +		if (argp->index)
> +			return -EINVAL;
> +		if (argp->width != dev->pix_format.width ||
> +		    argp->height != dev->pix_format.height ||
> +		    argp->pixel_format != dev->pix_format.pixelformat)
> +			return -EINVAL;
> +
> +		argp->type = V4L2_FRMIVAL_TYPE_DISCRETE;
> +		argp->discrete = dev->capture.timeperframe;
> +	} else {
> +		if (!vcam_find_format(argp->pixel_format) ||
> +		    !vcam_mode_has_framesize(dev, argp->pixel_format,
> +					     argp->width, argp->height))
> +			return -EINVAL;
> +
> +		if (argp->index)
> +			return -EINVAL;
> +
> +		argp->type = V4L2_FRMIVAL_TYPE_CONTINUOUS;
> +		argp->stepwise.min.numerator = 1;
> +		argp->stepwise.min.denominator = VCAM_FPS_MAX;
> +		argp->stepwise.max.numerator = 1;
> +		argp->stepwise.max.denominator = VCAM_FPS_MIN;
> +		argp->stepwise.step.numerator = 1;
> +		argp->stepwise.step.denominator = 1;
> +	}
> +
> +	return 0;
> +}
> +
> +static int vcam_vidioc_enum_framesizes(struct file *file, void *fh,
> +				       struct v4l2_frmsizeenum *argp)
> +{
> +	struct vcam *dev = video_drvdata(file);
> +
> +	return vcam_enum_framesizes(dev, argp);
> +}
> +
> +static int vcam_vidioc_enum_frameintervals(struct file *file, void *fh,
> +					   struct v4l2_frmivalenum *argp)
> +{
> +	struct vcam *dev = video_drvdata(file);
> +
> +	return vcam_enum_frameintervals(dev, argp);
> +}
> +
> +static int vcam_vidioc_enum_fmt_cap(struct file *file, void *fh,
> +				    struct v4l2_fmtdesc *f)
> +{
> +	struct vcam *dev;
> +
> +	dev = video_drvdata(file);
> +
> +	if (vcam_is_streaming(dev)) {
> +		const __u32 format = dev->pix_format.pixelformat;
> +
> +		if (f->index)
> +			return -EINVAL;
> +
> +		f->pixelformat = dev->pix_format.pixelformat;
> +		vcam_fmt_descr(f->description, sizeof(f->description), format);
> +	} else {
> +		u32 pixelformat;
> +
> +		if (vcam_mode_enum_format(dev, f->index, &pixelformat))
> +			return -EINVAL;
> +
> +		f->pixelformat = pixelformat;
> +		vcam_fmt_descr(f->description, sizeof(f->description),
> +			       pixelformat);
> +	}
> +	f->flags = 0;
> +	return 0;
> +}
> +
> +static int vcam_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
> +				     struct v4l2_format *fmt)
> +{
> +	struct vcam *dev;
> +
> +	dev = video_drvdata(file);
> +
> +	fmt->fmt.pix = dev->pix_format;
> +	return 0;
> +}
> +
> +static int vcam_vidioc_try_fmt_vid_cap(struct file *file, void *priv,
> +				       struct v4l2_format *fmt)
> +{
> +	struct vcam *dev = video_drvdata(file);
> +	struct v4l2_format try_fmt;
> +	int ret;
> +
> +	if (!V4L2_TYPE_IS_CAPTURE(fmt->type))
> +		return -EINVAL;
> +
> +	if (vcam_is_streaming(dev)) {
> +		if (!vcam_pix_format_eq(&dev->pix_format, &fmt->fmt.pix))
> +			return -EBUSY;
> +
> +		fmt->fmt.pix = dev->pix_format;
> +	}
> +
> +	try_fmt = *fmt;
> +	ret = vcam_set_format(&try_fmt);
> +	if (ret)
> +		return ret;
> +	if (!vcam_mode_allowed(dev, &try_fmt.fmt.pix))
> +		return -EINVAL;
> +	*fmt = try_fmt;
> +	return 0;
> +}
> +
> +static int vcam_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
> +				     struct v4l2_format *fmt)
> +{
> +	struct vcam *dev = video_drvdata(file);
> +	struct v4l2_format try_fmt = *fmt;
> +	int ret;
> +
> +	if (!V4L2_TYPE_IS_CAPTURE(fmt->type))
> +		return -EINVAL;
> +
> +	if (vcam_is_streaming(dev)) {
> +		if (!vcam_pix_format_eq(&dev->pix_format, &fmt->fmt.pix))
> +			return -EBUSY;
> +
> +		fmt->fmt.pix = dev->pix_format;
> +	}
> +
> +	ret = vcam_set_format(&try_fmt);
> +	if (ret)
> +		return ret;
> +
> +	if (!vcam_mode_allowed(dev, &try_fmt.fmt.pix))
> +		return -EINVAL;
> +
> +	if (vb2_is_busy(&dev->output_queue) &&
> +	    !vcam_pix_format_eq(&dev->pix_format, &try_fmt.fmt.pix))
> +		return -EBUSY;
> +
> +	dev->pix_format = try_fmt.fmt.pix;
> +	*fmt = try_fmt;
> +	return 0;
> +}
> +
> +static int vcam_ioc_reqbufs(struct file *file, struct vcam *dev,
> +			    struct v4l2_requestbuffers *req)
> +{
> +	int ret = 0;
> +
> +	if (req->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
> +		return -EINVAL;
> +
> +	scoped_guard(mutex, &dev->lock)
> +	{
> +		if (vb2_queue_is_busy(&dev->output_queue, file)) {
> +			ret = -EBUSY;
> +			break;
> +		}
> +
> +		ret = vb2_reqbufs(&dev->output_queue, req);
> +		if (!ret)
> +			dev->output_queue.owner =
> +				req->count ? file->private_data : NULL;
> +	}
> +	return ret;
> +}
> +
> +static int vcam_ioc_querybuf(struct file *file, struct vcam *dev,
> +			     struct v4l2_buffer *buf)
> +{
> +	int ret = 0;
> +
> +	if (buf->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
> +		return -EINVAL;
> +
> +	scoped_guard(mutex, &dev->lock)
> +		ret = vb2_querybuf(&dev->output_queue, buf);
> +
> +	return ret;
> +}
> +
> +static ssize_t formats_show(struct device *dev, struct device_attribute *attr,
> +			    char *buf)
> +{
> +	struct vcam_format_entry {
> +		u32 fourcc;
> +		char name[5];
> +	};
> +	struct vcam_format_entry formats[VCAM_MAX_FORMATS];
> +	struct vcam_format_entry tmp;
> +	unsigned int count =
> +		min_t(unsigned int, VCAM_NR_FORMATS, VCAM_MAX_FORMATS);
> +	size_t len = 0;
> +	unsigned int i, j;
> +
> +	for (i = 0; i < count; i++) {
> +		formats[i].fourcc = vcam_formats[i].fourcc;
> +		vcam_fourcc_str(formats[i].name, formats[i].fourcc);
> +	}
> +
> +	for (i = 1; i < count; i++) {
> +		for (j = i; j > 0; j--) {
> +			if (strcmp(formats[j - 1].name, formats[j].name) <= 0)
> +				break;
> +			tmp = formats[j - 1];
> +			formats[j - 1] = formats[j];
> +			formats[j] = tmp;
> +		}
> +	}
> +
> +	for (i = 0; i < count; i++)
> +		len += sysfs_emit_at(buf, len, "%s%s", i ? " " : "",
> +				     formats[i].name);
> +
> +	len += sysfs_emit_at(buf, len, "\n");
> +	return len;
> +}
> +
> +static ssize_t max_width_show(struct device *dev, struct device_attribute *attr,
> +			      char *buf)
> +{
> +	return sysfs_emit(buf, "%u\n", VCAM_MAX_WIDTH);
> +}
> +
> +static ssize_t max_height_show(struct device *dev,
> +			       struct device_attribute *attr, char *buf)
> +{
> +	return sysfs_emit(buf, "%u\n", VCAM_MAX_HEIGHT);
> +}
> +
> +static ssize_t max_frames_show(struct device *dev,
> +			       struct device_attribute *attr, char *buf)
> +{
> +	return sysfs_emit(buf, "%u\n", VCAM_MAX_FRAMES);
> +}
> +
> +static DEVICE_ATTR_RO(formats);
> +static DEVICE_ATTR_RO(max_frames);
> +static DEVICE_ATTR_RO(max_height);
> +static DEVICE_ATTR_RO(max_width);
> +
> +static struct attribute *vcam_attrs[] = {
> +	&dev_attr_formats.attr,
> +	&dev_attr_max_frames.attr,
> +	&dev_attr_max_height.attr,
> +	&dev_attr_max_width.attr,
> +	NULL,
> +};
> +
> +static const struct attribute_group vcam_attr_group = {
> +	.attrs = vcam_attrs,
> +};
> +
> +static const struct attribute_group *vcam_attr_groups[] = {
> +	&vcam_attr_group,
> +	NULL,
> +};
> +
> +static int vcam_ioc_alloc(struct file *file, struct vcam *dev, u32 nr_frames,
> +			  void __user *frames_user, enum vb2_memory memory)
> +{
> +	struct v4l2_requestbuffers req = {
> +		.type = V4L2_BUF_TYPE_VIDEO_OUTPUT,
> +		.memory = memory,
> +	};
> +	struct v4l2_buffer buf;
> +	struct vcam_frame *frames = NULL;
> +	unsigned int i;
> +	int ret;
> +
> +	if (memory == VB2_MEMORY_DMABUF &&
> +	    !dev->output_queue.mem_ops->attach_dmabuf)
> +		return -EOPNOTSUPP;
> +
> +	if (!frames_user)
> +		return -EINVAL;
> +
> +	if (nr_frames) {
> +		frames = kcalloc(nr_frames, sizeof(*frames), GFP_KERNEL);
> +		if (!frames)
> +			return -ENOMEM;
> +	}
> +
> +	if (copy_from_user(frames, frames_user, nr_frames * sizeof(*frames))) {
> +		ret = -EFAULT;
> +		goto out_free;
> +	}
> +
> +	req.count = nr_frames;
> +	ret = vcam_ioc_reqbufs(file, dev, &req);
> +	if (ret)
> +		goto out_free;
> +
> +	if (req.count != nr_frames) {
> +		struct v4l2_requestbuffers req_free = {
> +			.type = V4L2_BUF_TYPE_VIDEO_OUTPUT,
> +			.memory = memory,
> +			.count = 0,
> +		};
> +
> +		vcam_ioc_reqbufs(file, dev, &req_free);
> +		ret = -ENOMEM;
> +		goto out_free;
> +	}
> +
> +	dev->output_memory = memory;
> +
> +	for (i = 0; i < nr_frames; i++) {
> +		memset(&buf, 0, sizeof(buf));
> +		buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
> +		buf.memory = memory;
> +		buf.index = i;
> +
> +		ret = vcam_ioc_querybuf(file, dev, &buf);
> +		if (ret)
> +			goto out_free_reqbufs;
> +
> +		frames[i].index = i;
> +		frames[i].length = buf.length;
> +	}
> +
> +	if (copy_to_user(frames_user, frames, nr_frames * sizeof(*frames)))
> +		ret = -EFAULT;
> +
> +out_free_reqbufs:
> +	if (ret) {
> +		struct v4l2_requestbuffers req_free = {
> +			.type = V4L2_BUF_TYPE_VIDEO_OUTPUT,
> +			.memory = memory,
> +			.count = 0,
> +		};
> +
> +		vcam_ioc_reqbufs(file, dev, &req_free);
> +		dev->output_memory = VB2_MEMORY_DMABUF;
> +	}
> +out_free:
> +	kfree(frames);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int vcam_ioc_queue(struct file *file, struct vcam *dev,
> +			  struct vcam_ioc_queue *queue)
> +{
> +	struct v4l2_buffer buf = {
> +		.type = V4L2_BUF_TYPE_VIDEO_OUTPUT,
> +		.memory = dev->output_memory,
> +		.index = queue->index,
> +		.bytesused = queue->length,
> +	};
> +	u32 remainder;
> +	int ret;
> +
> +	if (queue->reserved)
> +		return -EINVAL;
> +
> +	if (dev->output_memory == VB2_MEMORY_DMABUF) {
> +		buf.m.fd = queue->fd;
> +		buf.length = dev->pix_format.sizeimage;
> +	}
> +
> +	buf.timestamp.tv_sec =
> +		div_u64_rem(queue->timestamp, NSEC_PER_SEC, &remainder);
> +	buf.timestamp.tv_usec = remainder / NSEC_PER_USEC;
> +
> +	scoped_guard(mutex, &dev->lock)
> +	{
> +		if (vb2_queue_is_busy(&dev->output_queue, file)) {
> +			ret = -EBUSY;
> +			break;
> +		}
> +
> +		if (vb2_is_streaming(&dev->capture_queue) &&
> +		    !vb2_is_streaming(&dev->output_queue)) {
> +			ret = vb2_streamon(&dev->output_queue, buf.type);
> +			if (ret)
> +				break;
> +		}
> +
> +		ret = vb2_qbuf(&dev->output_queue, NULL, &buf);
> +	}
> +
> +	return ret;
> +}
> +
> +static int vcam_ioc_dequeue(struct file *file, struct vcam *dev,
> +			    struct vcam_ioc_dequeue *queue)
> +{
> +	struct v4l2_buffer buf = {
> +		.type = V4L2_BUF_TYPE_VIDEO_OUTPUT,
> +		.memory = dev->output_memory,
> +	};
> +	int ret;
> +
> +	scoped_guard(mutex, &dev->lock)
> +	{
> +		if (vb2_queue_is_busy(&dev->output_queue, file)) {
> +			ret = -EBUSY;
> +			break;
> +		}
> +
> +		ret = vb2_dqbuf(&dev->output_queue, &buf,
> +				file->f_flags & O_NONBLOCK);
> +	}
> +	if (ret)
> +		return ret;
> +
> +	queue->index = buf.index;
> +	queue->length = buf.bytesused;
> +	queue->timestamp = (u64)buf.timestamp.tv_sec * NSEC_PER_SEC +
> +			   (u64)buf.timestamp.tv_usec * NSEC_PER_USEC;
> +	return 0;
> +}
> +
> +static int vcam_ioc_wait(struct vcam *dev, struct vcam_ioc_wait *wait)
> +{
> +	const struct v4l2_pix_format *pix = &dev->pix_format;
> +	struct vcam_mode mode;
> +	int ret;
> +
> +	if (wait->reserved)
> +		return -EINVAL;
> +
> +	if (wait->mask & ~VCAM_STATUS_MASK)
> +		return -EINVAL;
> +
> +	if (!wait->mode)
> +		return -EINVAL;
> +
> +	if (wait->mask) {
> +		ret = wait_event_interruptible(dev->status_waitq,
> +					       vcam_status_mask_ready(dev,
> +								      wait->mask));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	wait->status = vcam_status_read(dev);
> +	mode = (struct vcam_mode){
> +		.width = pix->width,
> +		.height = pix->height,
> +		.pixelformat = pix->pixelformat,
> +		.colorspace = pix->colorspace,
> +		.stride = pix->bytesperline,
> +	};
> +
> +	if (copy_to_user(u64_to_user_ptr(wait->mode), &mode, sizeof(mode)))
> +		return -EFAULT;
> +	return 0;
> +}
> +
> +static long vcam_output_ioctl_core(struct file *file, unsigned int cmd,
> +				   void *arg)
> +{
> +	struct vcam *dev = file->private_data;
> +	long ret = 0;
> +
> +	switch (cmd) {
> +	case VCAM_IOC_QUEUE:
> +		ret = vcam_ioc_queue(file, dev, arg);
> +		break;
> +	case VCAM_IOC_DEQUEUE:
> +		ret = vcam_ioc_dequeue(file, dev, arg);
> +		break;
> +	case VCAM_IOC_WAIT:
> +		ret = vcam_ioc_wait(dev, arg);
> +		break;
> +	default:
> +		ret = -EOPNOTSUPP;
> +		break;
> +	}
> +
> +	return ret;
> +}
> +
> +static long vcam_ioctl_common(struct file *file, unsigned int cmd,
> +			      unsigned long arg)
> +{
> +	void __user *argp = (void __user *)arg;
> +	void *karg;
> +	size_t size;
> +	long ret;
> +
> +	switch (cmd) {
> +	case VCAM_IOC_QUEUE:
> +		size = sizeof(struct vcam_ioc_queue);
> +		break;
> +	case VCAM_IOC_DEQUEUE:
> +		size = sizeof(struct vcam_ioc_dequeue);
> +		break;
> +	case VCAM_IOC_WAIT:
> +		size = sizeof(struct vcam_ioc_wait);
> +		break;
> +	default:
> +		return -ENOTTY;
> +	}
> +
> +	if (size > SZ_4K)
> +		return -ENOTTY;
> +
> +	karg = kzalloc(size, GFP_KERNEL);
> +	if (!karg)
> +		return -ENOMEM;
> +
> +	if (copy_from_user(karg, argp, size)) {
> +		ret = -EFAULT;
> +		goto out_free;
> +	}
> +
> +	ret = vcam_output_ioctl_core(file, cmd, karg);
> +	if (ret)
> +		goto out_free;
> +
> +	if (copy_to_user(argp, karg, size)) {
> +		ret = -EFAULT;
> +		goto out_free;
> +	}
> +
> +	ret = 0;
> +out_free:
> +	kfree(karg);
> +	return ret;
> +}
> +
> +static void __vcam_release(struct vcam *dev)
> +{
> +	if (!dev->vdev)
> +		return;
> +
> +	vb2_queue_release(&dev->output_queue);
> +	vb2_queue_release(&dev->capture_queue);
> +	kfree(dev->modes);
> +	dev->modes = NULL;
> +	dev->nr_modes = 0;
> +
> +	if (video_is_registered(dev->vdev))
> +		video_unregister_device(dev->vdev);
> +	else
> +		video_device_release(dev->vdev);
> +
> +	v4l2_device_unregister(&dev->v4l2_dev);
> +
> +	dev->vdev = NULL;
> +	dev->device_nr = -1;
> +}
> +
> +static void vcam_release(struct kref *ref)
> +{
> +	struct vcam *dev;
> +
> +	dev = container_of(ref, struct vcam, ref);
> +
> +	if (!test_bit(VCAM_FLAG_CREATING, &dev->flags) || dev->device_nr < 0) {
> +		kfree(dev);
> +		return;
> +	}
> +
> +	__vcam_release(dev);
> +	kfree(dev);
> +}
> +
> +static int __vcam_close(struct inode *inode, struct file *file)
> +{
> +	struct vcam *dev = file->private_data;
> +
> +	if (dev->vdev && video_is_registered(dev->vdev))
> +		video_unregister_device(dev->vdev);
> +
> +	vb2_queue_release(&dev->output_queue);
> +
> +	dev->output_memory = VB2_MEMORY_DMABUF;
> +	kfree(dev->modes);
> +	dev->modes = NULL;
> +	dev->nr_modes = 0;
> +
> +	kref_put(&dev->ref, vcam_release);
> +	return 0;
> +}
> +
> +static int vcam_open(struct inode *inode, struct file *file)
> +{
> +	struct vcam *dev;
> +	int ret = nonseekable_open(inode, file);
> +
> +	if (ret)
> +		return ret;
> +
> +	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
> +	if (!dev)
> +		return -ENOMEM;
> +
> +	kref_init(&dev->ref);
> +	dev->device_nr = -1;
> +	file->private_data = dev;
> +	return 0;
> +}
> +
> +static int vcam_close(struct inode *inode, struct file *file)
> +{
> +	struct vcam *dev = file->private_data;
> +	int ret = 0;
> +
> +	if (!dev)
> +		return 0;
> +
> +	if (test_bit(VCAM_FLAG_CREATING, &dev->flags) && dev->device_nr >= 0)
> +		ret = __vcam_close(inode, file);
> +	else
> +		kref_put(&dev->ref, vcam_release);
> +
> +	file->private_data = NULL;
> +	return ret;
> +}
> +
> +static __poll_t vcam_poll(struct file *file, struct poll_table_struct *pts)
> +{
> +	struct vcam *dev = file->private_data;
> +
> +	if (!dev || !test_bit(VCAM_FLAG_CREATING, &dev->flags) ||
> +	    !test_bit(VCAM_FLAG_READY, &dev->flags) || dev->device_nr < 0)
> +		return POLLERR;
> +
> +	return vb2_core_poll(&dev->output_queue, file, pts);
> +}
> +
> +static int vcam_mmap(struct file *file, struct vm_area_struct *vma)
> +{
> +	struct vcam *dev = file->private_data;
> +
> +	if (!dev || !test_bit(VCAM_FLAG_CREATING, &dev->flags) ||
> +	    !test_bit(VCAM_FLAG_READY, &dev->flags) || dev->device_nr < 0)
> +		return -ENOTTY;
> +
> +	return vb2_mmap(&dev->output_queue, vma);
> +}
> +
> +static int vcam_vidioc_g_parm(struct file *file, void *priv,
> +			      struct v4l2_streamparm *parm)
> +{
> +	struct vcam *dev;
> +
> +	if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
> +		return -EINVAL;
> +
> +	dev = video_drvdata(file);
> +	parm->parm.capture = dev->capture;
> +	return 0;
> +}
> +
> +static int vcam_vidioc_s_parm(struct file *file, void *priv,
> +			      struct v4l2_streamparm *parm)
> +{
> +	struct v4l2_fract *tpf = &parm->parm.capture.timeperframe;
> +	struct vcam *dev = video_drvdata(file);
> +
> +	if (!vcam_tpf_valid(tpf))
> +		return -EINVAL;
> +
> +	if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
> +		return -EINVAL;
> +
> +	dev->capture.timeperframe = *tpf;
> +	parm->parm.capture = dev->capture;
> +	return 0;
> +}
> +
> +static int vcam_vidioc_enum_input(struct file *file, void *fh,
> +				  struct v4l2_input *inp)
> +{
> +	struct vcam *dev;
> +	__u32 index = inp->index;
> +
> +	if (index != 0)
> +		return -EINVAL;
> +
> +	memset(inp, 0, sizeof(*inp));
> +
> +	inp->index = index;
> +	strscpy(inp->name, "vcam", sizeof(inp->name));
> +	inp->type = V4L2_INPUT_TYPE_CAMERA;
> +	inp->audioset = 0;
> +	inp->tuner = 0;
> +	inp->status = 0;
> +
> +	dev = video_drvdata(file);
> +	if (!vb2_is_streaming(&dev->output_queue))
> +		inp->status |= V4L2_IN_ST_NO_SIGNAL;
> +
> +	return 0;
> +}
> +
> +static int vcam_vidioc_g_input(struct file *file, void *fh, unsigned int *i)
> +{
> +	*i = 0;
> +	return 0;
> +}
> +
> +static int vcam_vidioc_s_input(struct file *file, void *fh, unsigned int i)
> +{
> +	if (i == 0)
> +		return 0;
> +
> +	return -EINVAL;
> +}
> +
> +static int vcam_vidioc_streamon(struct file *file, void *fh,
> +				enum v4l2_buf_type type)
> +{
> +	struct vcam *dev = video_drvdata(file);
> +	int ret;
> +
> +	if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
> +		return -EINVAL;
> +
> +	if (vb2_queue_is_busy(&dev->capture_queue, file))
> +		return -EBUSY;
> +
> +	ret = vb2_streamon(&dev->capture_queue, type);
> +	if (ret)
> +		return ret;
> +
> +	if (vb2_get_num_buffers(&dev->output_queue)) {
> +		ret = vb2_streamon(&dev->output_queue,
> +				   V4L2_BUF_TYPE_VIDEO_OUTPUT);
> +		if (ret) {
> +			vb2_streamoff(&dev->capture_queue, type);
> +			return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int vcam_vidioc_streamoff(struct file *file, void *fh,
> +				 enum v4l2_buf_type type)
> +{
> +	struct vcam *dev = video_drvdata(file);
> +	int ret;
> +
> +	if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
> +		return -EINVAL;
> +
> +	if (vb2_queue_is_busy(&dev->capture_queue, file))
> +		return -EBUSY;
> +
> +	ret = vb2_streamoff(&dev->capture_queue, type);
> +	if (ret)
> +		return ret;
> +
> +	if (vb2_get_num_buffers(&dev->output_queue))
> +		vb2_streamoff(&dev->output_queue, V4L2_BUF_TYPE_VIDEO_OUTPUT);
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_ioctl_ops vcam_ioctl_ops = {
> +	.vidioc_querycap = &vcam_vidioc_querycap,
> +	.vidioc_enum_framesizes = &vcam_vidioc_enum_framesizes,
> +	.vidioc_enum_frameintervals = &vcam_vidioc_enum_frameintervals,
> +	.vidioc_enum_input = &vcam_vidioc_enum_input,
> +	.vidioc_g_input = &vcam_vidioc_g_input,
> +	.vidioc_s_input = &vcam_vidioc_s_input,
> +	.vidioc_enum_fmt_vid_cap = &vcam_vidioc_enum_fmt_cap,
> +	.vidioc_g_fmt_vid_cap = &vcam_vidioc_g_fmt_vid_cap,
> +	.vidioc_s_fmt_vid_cap = &vcam_vidioc_s_fmt_vid_cap,
> +	.vidioc_try_fmt_vid_cap = &vcam_vidioc_try_fmt_vid_cap,
> +	.vidioc_g_parm = &vcam_vidioc_g_parm,
> +	.vidioc_s_parm = &vcam_vidioc_s_parm,
> +
> +	.vidioc_reqbufs = &vb2_ioctl_reqbufs,
> +	.vidioc_create_bufs = &vb2_ioctl_create_bufs,
> +	.vidioc_prepare_buf = &vb2_ioctl_prepare_buf,
> +	.vidioc_querybuf = &vb2_ioctl_querybuf,
> +	.vidioc_qbuf = &vb2_ioctl_qbuf,
> +	.vidioc_dqbuf = &vb2_ioctl_dqbuf,
> +	.vidioc_expbuf = &vb2_ioctl_expbuf,
> +	.vidioc_streamon = &vcam_vidioc_streamon,
> +	.vidioc_streamoff = &vcam_vidioc_streamoff,
> +};
> +
> +static enum vb2_buffer_state vcam_buf_fill(struct vcam *dev,
> +					   struct vcam_buf *buf,
> +					   const void *src, u32 src_len,
> +					   u64 timestamp)
> +{
> +	struct vb2_buffer *vb = &buf->vb.vb2_buf;
> +	u32 sequence;
> +	void *dst;
> +
> +	dst = vb2_plane_vaddr(vb, 0);
> +	if (!dst)
> +		return VB2_BUF_STATE_ERROR;
> +
> +	if (!src_len || src_len > dev->pix_format.sizeimage)
> +		src_len = dev->pix_format.sizeimage;
> +
> +	if (!src)
> +		return VB2_BUF_STATE_ERROR;
> +
> +	memcpy(dst, src, src_len);
> +
> +	sequence = (u32)(atomic_inc_return(&dev->sequence) - 1);
> +
> +	vb->timestamp = timestamp ? timestamp : ktime_get_ns();
> +	buf->vb.sequence = sequence;
> +	buf->vb.field = dev->pix_format.field;
> +	vb2_set_plane_payload(vb, 0, src_len);
> +
> +	return VB2_BUF_STATE_DONE;
> +}
> +
> +static bool vcam_buf_flip(struct vcam *dev, struct vb2_buffer *out_vb,
> +			  struct vcam_buf *cap_buf, u32 bytesused)
> +{
> +	struct vb2_buffer *cap_vb = &cap_buf->vb.vb2_buf;
> +	u32 sequence;
> +
> +	if (!out_vb->planes[0].dbuf || !cap_vb->planes[0].dbuf)
> +		return false;
> +
> +	if (out_vb->planes[0].dbuf != cap_vb->planes[0].dbuf)
> +		return false;
> +
> +	if (!bytesused)
> +		bytesused = dev->pix_format.sizeimage;
> +	if (bytesused > vb2_plane_size(cap_vb, 0))
> +		bytesused = vb2_plane_size(cap_vb, 0);
> +
> +	sequence = (u32)(atomic_inc_return(&dev->sequence) - 1);
> +
> +	cap_vb->timestamp = out_vb->timestamp ? out_vb->timestamp :
> +						ktime_get_ns();
> +	cap_buf->vb.sequence = sequence;
> +	cap_buf->vb.field = dev->pix_format.field;
> +	vb2_set_plane_payload(cap_vb, 0, bytesused);
> +
> +	return true;
> +}
> +
> +static bool vcam_buf_pair_dequeue(struct vcam *dev, struct vcam_buf **out_buf,
> +				  struct vcam_buf **cap_buf)
> +{
> +	unsigned long flags;
> +	bool dequeued = false;
> +
> +	spin_lock_irqsave(&dev->frame_lock, flags);
> +	if (!list_empty(&dev->output_list) && !list_empty(&dev->capture_list)) {
> +		*out_buf = list_first_entry(&dev->output_list, struct vcam_buf,
> +					    list);
> +		list_del(&(*out_buf)->list);
> +		*cap_buf = list_first_entry(&dev->capture_list, struct vcam_buf,
> +					    list);
> +		list_del(&(*cap_buf)->list);
> +		dequeued = true;
> +	}
> +	spin_unlock_irqrestore(&dev->frame_lock, flags);
> +	return dequeued;
> +}
> +
> +static void vcam_dequeue_frames(struct vcam *data)
> +{
> +	const struct vcam_format *format;
> +	enum vb2_buffer_state cap_state;
> +	struct vcam_buf *cap_buf;
> +	struct vcam_buf *out_buf;
> +	struct vb2_buffer *vb;
> +	bool zero_copy;
> +	u32 bytesused;
> +	void *src;
> +
> +	if (!vcam_is_streaming(data))
> +		return;
> +
> +	format = vcam_find_format(data->pix_format.pixelformat);
> +	while (vcam_buf_pair_dequeue(data, &out_buf, &cap_buf)) {
> +		cap_state = VB2_BUF_STATE_DONE;
> +		vb = &out_buf->vb.vb2_buf;
> +		bytesused = vb2_get_plane_payload(vb, 0);
> +		if (!bytesused || bytesused > data->pix_format.sizeimage)
> +			bytesused = data->pix_format.sizeimage;
> +
> +		if (bytesused < data->pix_format.sizeimage &&
> +		    (!format || !(format->flags & VCAM_COMPRESSED))) {
> +			cap_state = VB2_BUF_STATE_ERROR;
> +			goto out_done;
> +		}
> +
> +		zero_copy = vcam_buf_flip(data, vb, cap_buf, bytesused);
> +		if (!zero_copy &&
> +		    (!(out_buf->flags & VCAM_BUF_FLAG_MAPPABLE) ||
> +		     !(cap_buf->flags & VCAM_BUF_FLAG_MAPPABLE))) {
> +			dev_dbg(&data->vdev->dev,
> +				"unshared unmappable capture and output");
> +			cap_state = VB2_BUF_STATE_ERROR;
> +			goto out_done;
> +		}
> +		if (!zero_copy) {
> +			src = vb2_plane_vaddr(vb, 0);
> +			if (!src) {
> +				cap_state = VB2_BUF_STATE_ERROR;
> +				goto out_done;
> +			}
> +
> +			cap_state = vcam_buf_fill(data, cap_buf, src, bytesused,
> +						  vb->timestamp);
> +		}
> +out_done:
> +		vb2_buffer_done(&cap_buf->vb.vb2_buf, cap_state);
> +
> +		if (cap_state == VB2_BUF_STATE_ERROR)
> +			vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
> +		else
> +			vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
> +	}
> +}
> +
> +static int vcam_vdev_open(struct file *file)
> +{
> +	struct vcam *dev;
> +	int ret;
> +
> +	dev = video_drvdata(file);
> +	if (test_and_set_bit(VCAM_FLAG_IS_OPEN, &dev->flags))
> +		return -EBUSY;
> +	if (dev->device_nr < 0 || !test_bit(VCAM_FLAG_READY, &dev->flags)) {
> +		clear_bit(VCAM_FLAG_IS_OPEN, &dev->flags);
> +		return -ENODEV;
> +	}
> +
> +	ret = v4l2_fh_open(file);
> +	if (ret) {
> +		clear_bit(VCAM_FLAG_IS_OPEN, &dev->flags);
> +		return ret;
> +	}
> +
> +	kref_get(&dev->ref);
> +	return 0;
> +}
> +
> +static int vcam_vdev_close(struct file *file)
> +{
> +	struct vcam *dev;
> +	int ret;
> +
> +	dev = video_drvdata(file);
> +	ret = _vb2_fop_release(file, NULL);
> +	clear_bit(VCAM_FLAG_IS_OPEN, &dev->flags);
> +
> +	kref_put(&dev->ref, vcam_release);
> +	return ret;
> +}
> +
> +static const struct v4l2_file_operations vcam_vdev_fops = {
> +	.owner = THIS_MODULE,
> +	.open = vcam_vdev_open,
> +	.release = vcam_vdev_close,
> +	.poll = vb2_fop_poll,
> +	.mmap = vb2_fop_mmap,
> +	.unlocked_ioctl = video_ioctl2,
> +};
> +
> +static int vcam_ioc_create_validate(struct vcam_ioc_create *config,
> +				    char *card_label)
> +{
> +	long len, i;
> +
> +	if (config->device_nr != 0)
> +		return -EINVAL;
> +	if (config->reserved)
> +		return -EINVAL;
> +	if (!config->nr_modes || config->nr_modes > VCAM_MAX_MODES)
> +		return -EINVAL;
> +	if (!config->modes)
> +		return -EINVAL;
> +	if (config->nr_frames > VCAM_MAX_FRAMES)
> +		return -E2BIG;
> +	if (config->nr_frames < VCAM_MIN_FRAMES)
> +		return -EINVAL;
> +	if (!config->frames)
> +		return -EINVAL;
> +
> +	memset(card_label, 0, VCAM_CARD_LABEL_MAX);
> +	len = strncpy_from_user(card_label,
> +				u64_to_user_ptr(config->device_name),
> +				VCAM_CARD_LABEL_MAX);
> +	if (len < 0)
> +		return -EFAULT;
> +	if (len >= VCAM_CARD_LABEL_MAX)
> +		return -E2BIG;
> +	if (!len)
> +		return -EINVAL;
> +	if (!isalnum((unsigned char)card_label[0]))
> +		return -EINVAL;
> +	for (i = 0; i < len; i++) {
> +		if (!isalnum((unsigned char)card_label[i]) &&
> +		    !isspace((unsigned char)card_label[i]))
> +			return -EINVAL;
> +	}
> +	if (!isalnum((unsigned char)card_label[len - 1]))
> +		return -EINVAL;
> +
> +	return len;
> +}
> +
> +static int vcam_vb2_queue_setup(struct vb2_queue *queue,
> +				unsigned int *nr_buffers,
> +				unsigned int *nr_planes, unsigned int sizes[],
> +				struct device *alloc_devs[])
> +{
> +	struct vcam *data = vb2_get_drv_priv(queue);
> +	unsigned int sizeimage = data->pix_format.sizeimage;
> +
> +	if (!sizeimage)
> +		return -EINVAL;
> +
> +	if (*nr_buffers < VCAM_MIN_FRAMES)
> +		*nr_buffers = VCAM_MIN_FRAMES;
> +	if (*nr_buffers > VCAM_MAX_FRAMES)
> +		*nr_buffers = VCAM_MAX_FRAMES;
> +
> +	if (*nr_planes)
> +		return sizes[0] < sizeimage ? -EINVAL : 0;
> +
> +	*nr_planes = 1;
> +	sizes[0] = sizeimage;
> +	return 0;
> +}
> +
> +static int vcam_vb2_buf_prepare(struct vb2_buffer *vb)
> +{
> +	struct vcam *data = vb2_get_drv_priv(vb->vb2_queue);
> +	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
> +	struct vcam_buf *buf = container_of(vbuf, struct vcam_buf, vb);
> +	unsigned int sizeimage = data->pix_format.sizeimage;
> +	unsigned int bytesused;
> +	void *vaddr;
> +
> +	if (vb2_plane_size(vb, 0) < sizeimage)
> +		return -EINVAL;
> +
> +	vbuf->field = data->pix_format.field;
> +	bytesused = vb2_get_plane_payload(vb, 0);
> +	if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type) && !bytesused)
> +		vb2_set_plane_payload(vb, 0, sizeimage);
> +
> +	buf->flags = VCAM_BUF_FLAG_MAPPABLE;
> +	if (vb->planes[0].dbuf) {
> +		vaddr = vb2_plane_vaddr(vb, 0);
> +		if (!vaddr)
> +			buf->flags &= ~VCAM_BUF_FLAG_MAPPABLE;
> +	}
> +	return 0;
> +}
> +
> +static void vcam_vb2_buf_queue(struct vb2_buffer *vb)
> +{
> +	struct vcam *data = vb2_get_drv_priv(vb->vb2_queue);
> +	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
> +	struct vcam_buf *buf;
> +	unsigned long flags;
> +
> +	buf = container_of(vbuf, struct vcam_buf, vb);
> +
> +	if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) {
> +		spin_lock_irqsave(&data->frame_lock, flags);
> +		list_add_tail(&buf->list, &data->output_list);
> +		spin_unlock_irqrestore(&data->frame_lock, flags);
> +	} else {
> +		spin_lock_irqsave(&data->frame_lock, flags);
> +		list_add_tail(&buf->list, &data->capture_list);
> +		spin_unlock_irqrestore(&data->frame_lock, flags);
> +	}
> +
> +	vcam_dequeue_frames(data);
> +}
> +
> +static int vcam_vb2_prepare_streaming(struct vb2_queue *vq)
> +{
> +	return 0;
> +}
> +
> +static int vcam_vb2_start_streaming(struct vb2_queue *vq, unsigned int count)
> +{
> +	struct vcam *data = vb2_get_drv_priv(vq);
> +
> +	if (V4L2_TYPE_IS_CAPTURE(vq->type)) {
> +		atomic_set(&data->sequence, 0);
> +		vcam_status_update_stream(data, true);
> +	}
> +
> +	vcam_dequeue_frames(data);
> +	return 0;
> +}
> +
> +static void vcam_vb2_stop_streaming(struct vb2_queue *vq)
> +{
> +	struct vcam *data = vb2_get_drv_priv(vq);
> +	struct vcam_buf *buf, *tmp;
> +	unsigned long flags;
> +	LIST_HEAD(done_list);
> +
> +	if (V4L2_TYPE_IS_CAPTURE(vq->type))
> +		vcam_status_update_stream(data, false);
> +
> +	spin_lock_irqsave(&data->frame_lock, flags);
> +	list_splice_init(&data->output_list, &done_list);
> +	list_splice_init(&data->capture_list, &done_list);
> +	spin_unlock_irqrestore(&data->frame_lock, flags);
> +
> +	list_for_each_entry_safe(buf, tmp, &done_list, list)
> +		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
> +}
> +
> +static const struct vb2_ops vcam_vb2_ops = {
> +	.queue_setup = vcam_vb2_queue_setup,
> +	.buf_queue = vcam_vb2_buf_queue,
> +	.buf_prepare = vcam_vb2_buf_prepare,
> +	.prepare_streaming = vcam_vb2_prepare_streaming,
> +	.start_streaming = vcam_vb2_start_streaming,
> +	.stop_streaming = vcam_vb2_stop_streaming,
> +};
> +
> +static int vcam_ioc_create(struct file *file, struct vcam *dev,
> +			   struct vcam_ioc_create *config, char *card_label,
> +			   unsigned int len)
> +{
> +	struct vcam_mode *modes = NULL;
> +	struct v4l2_format try_fmt;
> +	struct video_device *vdev;
> +	struct vb2_queue *queue;
> +	struct v4l2_format fmt;
> +	long ret;
> +	u32 i;
> +
> +	strscpy(dev->v4l2_dev.name, "vcam", sizeof(dev->v4l2_dev.name));
> +
> +	ret = v4l2_device_register(NULL, &dev->v4l2_dev);
> +	if (ret)
> +		return ret;
> +
> +	vdev = video_device_alloc();
> +	if (!vdev) {
> +		ret = -ENOMEM;
> +		goto err_unregister;
> +	}
> +
> +	dev->vdev = vdev;
> +	video_set_drvdata(vdev, dev);
> +	memcpy(vdev->name, card_label, len);
> +	vdev->name[len] = '\0';
> +	vdev->vfl_type = VFL_TYPE_VIDEO;
> +	vdev->fops = &vcam_vdev_fops;
> +	vdev->ioctl_ops = &vcam_ioctl_ops;
> +	vdev->release = &video_device_release;
> +	vdev->minor = -1;
> +	vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
> +	vdev->vfl_dir = VFL_DIR_RX;
> +
> +	mutex_init(&dev->lock);
> +	spin_lock_init(&dev->frame_lock);
> +	spin_lock_init(&dev->status_lock);
> +	INIT_LIST_HEAD(&dev->capture_list);
> +	INIT_LIST_HEAD(&dev->output_list);
> +	dev->status = VCAM_STATUS_IDLE;
> +	dev->output_memory = VB2_MEMORY_DMABUF;
> +	init_waitqueue_head(&dev->status_waitq);
> +
> +	dev->vdev->v4l2_dev = &dev->v4l2_dev;
> +	dev->vdev->queue = &dev->capture_queue;
> +	dev->vdev->lock = &dev->lock;
> +	dev->capture.capability = 0;
> +	dev->capture.capturemode = 0;
> +	dev->capture.extendedmode = 0;
> +	dev->capture.readbuffers = VCAM_MIN_FRAMES;
> +	dev->capture.timeperframe.numerator = 1;
> +	dev->capture.timeperframe.denominator = 30;
> +
> +	if (!IS_ENABLED(CONFIG_DMA_SHARED_BUFFER) ||
> +	    !vb2_vmalloc_memops.attach_dmabuf) {
> +		ret = -EOPNOTSUPP;
> +		goto err_unregister;
> +	}
> +
> +	modes = kcalloc(config->nr_modes, sizeof(*modes), GFP_KERNEL);
> +	if (!modes) {
> +		ret = -ENOMEM;
> +		goto err_unregister;
> +	}
> +
> +	if (copy_from_user(modes, u64_to_user_ptr(config->modes),
> +			   config->nr_modes * sizeof(*modes))) {
> +		ret = -EFAULT;
> +		goto err_modes;
> +	}
> +
> +	for (i = 0; i < config->nr_modes; i++) {
> +		struct vcam_mode *mode = &modes[i];
> +
> +		if (!mode->width || !mode->height || !mode->pixelformat) {
> +			ret = -EINVAL;
> +			goto err_modes;
> +		}
> +
> +		if (memchr_inv(mode->reserved, 0, sizeof(mode->reserved))) {
> +			ret = -EINVAL;
> +			goto err_modes;
> +		}
> +
> +		fmt = (struct v4l2_format){
> +			.type = V4L2_BUF_TYPE_VIDEO_OUTPUT,
> +			.fmt.pix = { .width = mode->width,
> +				     .height = mode->height,
> +				     .pixelformat = mode->pixelformat,
> +				     .colorspace = mode->colorspace,
> +				     .bytesperline = mode->stride,
> +				     .field = V4L2_FIELD_NONE }
> +		};
> +
> +		try_fmt = fmt;
> +		ret = vcam_set_format(&try_fmt);
> +		if (ret)
> +			goto err_modes;
> +
> +		if (try_fmt.fmt.pix.width != mode->width ||
> +		    try_fmt.fmt.pix.height != mode->height ||
> +		    try_fmt.fmt.pix.pixelformat != mode->pixelformat ||
> +		    (mode->colorspace != V4L2_COLORSPACE_DEFAULT &&
> +		     try_fmt.fmt.pix.colorspace != mode->colorspace) ||
> +		    (mode->stride &&
> +		     try_fmt.fmt.pix.bytesperline != mode->stride)) {
> +			ret = -EINVAL;
> +			goto err_modes;
> +		}
> +
> +		mode->colorspace = try_fmt.fmt.pix.colorspace;
> +		mode->stride = try_fmt.fmt.pix.bytesperline;
> +	}
> +
> +	sort(modes, config->nr_modes, sizeof(*modes), vcam_mode_cmp, NULL);
> +	for (i = 1; i < config->nr_modes; i++) {
> +		if (vcam_mode_equal(&modes[i - 1], &modes[i])) {
> +			ret = -EINVAL;
> +			goto err_modes;
> +		}
> +	}
> +
> +	dev->modes = modes;
> +	dev->nr_modes = config->nr_modes;
> +	modes = NULL;
> +	dev->pix_format = (struct v4l2_pix_format){
> +		.width = dev->modes[0].width,
> +		.height = dev->modes[0].height,
> +		.pixelformat = dev->modes[0].pixelformat,
> +		.colorspace = dev->modes[0].colorspace,
> +		.bytesperline = dev->modes[0].stride,
> +		.field = V4L2_FIELD_NONE,
> +	};
> +
> +	fmt = (struct v4l2_format){
> +		.type = V4L2_BUF_TYPE_VIDEO_OUTPUT,
> +		.fmt.pix = dev->pix_format,
> +	};
> +
> +	ret = vcam_set_format(&fmt);
> +	if (ret)
> +		goto err_unregister;
> +
> +	dev->pix_format = fmt.fmt.pix;
> +
> +	queue = &dev->capture_queue;
> +	queue->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> +	queue->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
> +	queue->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> +	queue->drv_priv = dev;
> +	queue->buf_struct_size = sizeof(struct vcam_buf);
> +	queue->ops = &vcam_vb2_ops;
> +	queue->mem_ops = &vb2_vmalloc_memops;
> +	queue->lock = &dev->lock;
> +	queue->dev = &dev->vdev->dev;
> +	ret = vb2_queue_init(queue);
> +	if (ret)
> +		goto err_unregister;
> +
> +	queue = &dev->output_queue;
> +	queue->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
> +	queue->io_modes = VB2_DMABUF;
> +	queue->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
> +	queue->drv_priv = dev;
> +	queue->buf_struct_size = sizeof(struct vcam_buf);
> +	queue->ops = &vcam_vb2_ops;
> +	queue->mem_ops = &vb2_vmalloc_memops;
> +	queue->lock = &dev->lock;
> +	queue->dev = &dev->vdev->dev;
> +	ret = vb2_queue_init(queue);
> +	if (ret)
> +		goto err_capture_queue;
> +
> +	ret = vcam_ioc_alloc(file, dev, config->nr_frames,
> +			     u64_to_user_ptr(config->frames),
> +			     VB2_MEMORY_DMABUF);
> +	if (ret)
> +		goto err_output_queue;
> +
> +	ret = video_register_device(dev->vdev, VFL_TYPE_VIDEO, -1);
> +	if (ret < 0)
> +		goto err_output_queue;
> +
> +	config->device_nr = dev->vdev->num;
> +	return 0;
> +
> +err_output_queue:
> +	vb2_queue_release(&dev->output_queue);
> +
> +err_capture_queue:
> +	vb2_queue_release(&dev->capture_queue);
> +
> +err_unregister:
> +	if (dev->vdev)
> +		video_device_release(dev->vdev);
> +	v4l2_device_unregister(&dev->v4l2_dev);
> +err_modes:
> +	kfree(dev->modes);
> +	dev->modes = NULL;
> +	dev->nr_modes = 0;
> +	kfree(modes);
> +	return ret;
> +}
> +
> +static long vcam_ioctl(struct file *file, unsigned int cmd, unsigned long parm)
> +{
> +	struct vcam *dev = file->private_data;
> +	char card_label[VCAM_CARD_LABEL_MAX];
> +	struct vcam_ioc_create config;
> +	long ret, len;
> +
> +	if (cmd != VCAM_IOC_CREATE) {
> +		if (!dev || !test_bit(VCAM_FLAG_CREATING, &dev->flags) ||
> +		    !test_bit(VCAM_FLAG_READY, &dev->flags) ||
> +		    dev->device_nr < 0)
> +			return -ENOTTY;
> +		return vcam_ioctl_common(file, cmd, parm);
> +	}
> +
> +	if (!dev)
> +		return -ENOTTY;
> +
> +	if (test_and_set_bit(VCAM_FLAG_CREATING, &dev->flags))
> +		return -EBUSY;
> +
> +	if (!parm) {
> +		ret = -EINVAL;
> +		goto err_clear;
> +	}
> +
> +	if (copy_from_user(&config, (void *)parm, sizeof(config))) {
> +		ret = -EFAULT;
> +		goto err_clear;
> +	}
> +
> +	len = vcam_ioc_create_validate(&config, card_label);
> +	if (len < 0) {
> +		ret = len;
> +		goto err_clear;
> +	}
> +
> +	ret = vcam_ioc_create(file, dev, &config, card_label, len);
> +	if (ret)
> +		goto err_clear;
> +
> +	if (copy_to_user((void *)parm, &config, sizeof(config))) {
> +		ret = -EFAULT;
> +		goto err_release;
> +	}
> +
> +	dev->device_nr = dev->vdev->num;
> +	snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name), "vcam-%d",
> +		 dev->device_nr);
> +	set_bit(VCAM_FLAG_READY, &dev->flags);
> +	return 0;
> +
> +err_release:
> +	__vcam_release(dev);
> +
> +err_clear:
> +	clear_bit(VCAM_FLAG_CREATING, &dev->flags);
> +	return ret;
> +}
> +
> +static const struct file_operations vcam_fops = {
> +	.owner = THIS_MODULE,
> +	.open = vcam_open,
> +	.unlocked_ioctl = vcam_ioctl,
> +#ifdef CONFIG_COMPAT
> +	.compat_ioctl = vcam_ioctl,
> +#endif
> +	.poll = vcam_poll,
> +	.mmap = vcam_mmap,
> +	.release = vcam_close,
> +	.llseek = noop_llseek,
> +};
> +
> +static struct miscdevice vcam_misc = {
> +	.minor = MISC_DYNAMIC_MINOR,
> +	.name = "vcam",
> +	.fops = &vcam_fops,
> +	.groups = vcam_attr_groups,
> +};
> +
> +module_misc_device(vcam_misc);
> diff --git a/include/uapi/linux/vcam.h b/include/uapi/linux/vcam.h
> new file mode 100644
> index 000000000000..a4b4d611ac58
> --- /dev/null
> +++ b/include/uapi/linux/vcam.h
> @@ -0,0 +1,141 @@
> +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
> +/*
> + * Copyright (c) Jarkko Sakkinen 2025-2026
> + */
> +
> +#ifndef _UAPI_LINUX_VCAM_H
> +#define _UAPI_LINUX_VCAM_H
> +
> +#include <linux/types.h>
> +#include <linux/ioctl.h>
> +
> +#define VCAM_IOC_BASE 'v'
> +
> +/**
> + * DOC: vcam uAPI
> + *
> + * The ioctl API of /dev/vcam provides ioctls for creating DMA-BUF backed
> + * virtual capture devices, and pushing image frames for consumption.
> + *
> + * Frames are queued with %VCAM_IOC_QUEUE and recycled with %VCAM_IOC_DEQUEUE.
> + * Queueing without dequeuing eventually exhausts the output queue.
> + */
> +
> +/**
> + * enum vcam_status - Status bits
> + * @VCAM_STATUS_IDLE: Capture queue is not streaming.
> + * @VCAM_STATUS_STREAMING: Capture queue is streaming.
> + */
> +enum vcam_status {
> +	VCAM_STATUS_IDLE = 1U << 0,
> +	VCAM_STATUS_STREAMING = 1U << 1,
> +};
> +
> +/**
> + * struct vcam_mode - Supported capture mode
> + * @width: Frame width in pixels.
> + * @height: Frame height in pixels.
> + * @pixelformat: Four CC format code.
> + * @colorspace: V4L2 colorspace value.
> + * @stride: Bytes per line in the output format.
> + * @reserved: Reserved for future use. Must be set to zero.
> + */
> +struct vcam_mode {
> +	__u32 width;
> +	__u32 height;
> +	__u32 pixelformat;
> +	__u32 colorspace;
> +	__u32 stride;
> +	__u8 reserved[12];
> +};
> +
> +/**
> + * struct vcam_ioc_create - Create a virtual camera device
> + * @device_name: (input) User pointer to device name string.
> + * @device_nr: (output) Device number (must be 0 on input).
> + * @nr_modes: (input) Number of entries in @modes.
> + * @modes: (input) User pointer to an array of &struct vcam_mode.
> + * @reserved: Reserved for future use. Must be set to zero.
> + * @nr_frames: (input) Number of entries in @frames.
> + * @frames: (input/output) User pointer to an array of &struct vcam_frame.
> + */
> +struct vcam_ioc_create {
> +	__u64 device_name;
> +	__u32 device_nr;
> +	__u32 nr_modes;
> +	__u64 modes;
> +	__u32 reserved;
> +	__u32 nr_frames;
> +	__u64 frames;
> +};

For what it is worth, this needs to be reworked for +1 version.

@frames is cruft that has lost its purpose. I'll implement buffer
re-creation for the next version, when the mode changes (during
streaming mode is obviously locked).

Also now that there is a list of modes, there should be a field
for selecting the initial mode.

> +
> +/**
> + * struct vcam_frame - a frame descriptor
> + * @index: Frame index assigned by the driver.
> + * @length: Frame size in bytes.
> + */
> +struct vcam_frame {
> +	__u32 index;
> +	__u32 length;
> +};
> +
> +/**
> + * struct vcam_ioc_queue - Produce an output buffer
> + * @fd: (input) DMA-BUF file descriptor.
> + * @index: (input) Buffer index for %VCAM_IOC_QUEUE.
> + * @length: (input) Payload length in bytes for %VCAM_IOC_QUEUE.
> + * @reserved: Reserved for future use. Must be set to zero.
> + * @timestamp: (input) Timestamp in nanoseconds for %VCAM_IOC_QUEUE.
> + */
> +struct vcam_ioc_queue {
> +	__u32 fd;
> +	__u32 index;
> +	__u32 length;
> +	__u32 reserved;
> +	__u64 timestamp;
> +};
> +
> +/**
> + * struct vcam_ioc_dequeue - Dequeue an output buffer
> + * @index: (output) Buffer index for %VCAM_IOC_DEQUEUE.
> + * @length: (output) Payload length in bytes for %VCAM_IOC_DEQUEUE.
> + * @timestamp: (output) Timestamp in nanoseconds for %VCAM_IOC_DEQUEUE.
> + */
> +struct vcam_ioc_dequeue {
> +	__u32 index;
> +	__u32 length;
> +	__u64 timestamp;
> +};
> +
> +/**
> + * struct vcam_ioc_wait - Wait for capture status
> + * @mask: (input) Mask of status bits to wait for. Set to zero to return
> + *         immediately.
> + * @status: (output) Current status bit mask.
> + * @mode: (output) User pointer to &struct vcam_mode.
> + * @reserved: Reserved for future use. Must be set to zero.
> + */
> +struct vcam_ioc_wait {
> +	__u64 mask;
> +	__u64 status;
> +	__u64 mode;
> +	__u64 reserved;
> +};
> +
> +/**
> + * DOC: vcam ioctls
> + *
> + * %VCAM_IOC_CREATE: Creates a virtual camera device, stores the allowed capture
> + * modes, and associates output buffers described by &struct vcam_frame with
> + * DMA-BUF file descriptors.
> + * %VCAM_IOC_QUEUE: Enqueues an output buffer for capture.
> + * %VCAM_IOC_DEQUEUE: Dequeues a consumed output buffer for reuse.
> + * %VCAM_IOC_WAIT: Waits for the subset of status bits to activate and returns
> + * the current status and capture mode.
> + */
> +#define VCAM_IOC_CREATE _IOWR(VCAM_IOC_BASE, 0x00, struct vcam_ioc_create)
> +#define VCAM_IOC_QUEUE _IOW(VCAM_IOC_BASE, 0x01, struct vcam_ioc_queue)
> +#define VCAM_IOC_DEQUEUE _IOR(VCAM_IOC_BASE, 0x02, struct vcam_ioc_dequeue)
> +#define VCAM_IOC_WAIT _IOWR(VCAM_IOC_BASE, 0x04, struct vcam_ioc_wait)
> +
> +#endif /* _UAPI_LINUX_VCAM_H */
> -- 
> 2.52.0
> 

BR, Jarkko

^ permalink raw reply

* [RFC PATCH v2] media: Virtual camera driver
From: Jarkko Sakkinen @ 2026-02-02 20:44 UTC (permalink / raw)
  To: linux-media
  Cc: jani.nikula, anisse, oleksandr, linux-integrity, Jarkko Sakkinen,
	Mauro Carvalho Chehab, Hans Verkuil, Laurent Pinchart,
	Sakari Ailus, Jacopo Mondi, Ricardo Ribalda, open list

Already a quick Google survey backs strongly that OOT drivers (e.g.,
v4l2loopback) are the defacto solution for streaming phone cameras in
video conference calls, which puts confidential discussions at risk.

It can be also claimed that there's enough OOT usage in the wild that
possible security bugs could be considered as potential zerodays for the
benefit of malicious actors.

The situation has been stagnated for however many years, which is
unsastainable situation, and it further factors potential security
risks. Therefore, a driver is needed to address the popular use case.

vcam is a DMA-BUF backed virtual camera driver capable of creating video
capture devices to which data can be streamed through /dev/vcam after
calling VCAM_IOC_CREATE. Frames are pushed with VCAM_IOC_QUEUE and recycled
with VCAM_IOC_DEQUEUE. Zero-copy semantics are supported for shared DMA-BUF
between capture and output.

This enables efficient implementation of software, which can manage network
video streams from phone cameras, and map those streams to video devices.

PipeWire or any other specific pick of userspace software cannot really
address the issue at scale, as e.g., the use of v4l2loopback is both wide
and scattered.

Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
---
v2:
- Added motivation based on feedback to v1.
- Provide a list of allowed modes for /dev/videoX in VCAM_IOC_CREATE.
- Merged VCAM_IOC_WAIT and VCAM_IOC_STATUS.
- Return state with operation flags in VCAM_IOC_WAIT.
- Test program: https://git.kernel.org/pub/scm/linux/kernel/git/jarkko/vcam-test.git
---
 .../driver-api/media/drivers/index.rst        |    1 +
 .../driver-api/media/drivers/vcam.rst         |   16 +
 MAINTAINERS                                   |    8 +
 drivers/media/Kconfig                         |   13 +
 drivers/media/Makefile                        |    1 +
 drivers/media/vcam.c                          | 1935 +++++++++++++++++
 include/uapi/linux/vcam.h                     |  141 ++
 7 files changed, 2115 insertions(+)
 create mode 100644 Documentation/driver-api/media/drivers/vcam.rst
 create mode 100644 drivers/media/vcam.c
 create mode 100644 include/uapi/linux/vcam.h

diff --git a/Documentation/driver-api/media/drivers/index.rst b/Documentation/driver-api/media/drivers/index.rst
index 7f6f3dcd5c90..211cafc9c070 100644
--- a/Documentation/driver-api/media/drivers/index.rst
+++ b/Documentation/driver-api/media/drivers/index.rst
@@ -27,6 +27,7 @@ Video4Linux (V4L) drivers
 	zoran
 	ccs/ccs
 	ipu6
+	vcam
 
 
 Digital TV drivers
diff --git a/Documentation/driver-api/media/drivers/vcam.rst b/Documentation/driver-api/media/drivers/vcam.rst
new file mode 100644
index 000000000000..b5a23144ebee
--- /dev/null
+++ b/Documentation/driver-api/media/drivers/vcam.rst
@@ -0,0 +1,16 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+===========================
+vcam: Virtual Camera Driver
+===========================
+
+Theory of Operation
+-------------------
+
+.. kernel-doc:: drivers/media/vcam.c
+   :doc: Theory of Operation
+
+Driver uAPI
+-----------
+
+.. kernel-doc:: include/uapi/linux/vcam.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 6863d5fa07a1..b8444ff48716 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27504,6 +27504,14 @@ S:	Maintained
 F:	drivers/media/common/videobuf2/*
 F:	include/media/videobuf2-*
 
+VCAM V4L2 DRIVER
+M:	Jarkko Sakkinen <jarkko@kernel.org>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+T:	git git://git.kernel.org/pub/scm/linux/kernel/git/jarkko/linux-tpmdd.git
+F:	drivers/media/vcam.c
+F:	include/uapi/linux/vcam.h
+
 VIDTV VIRTUAL DIGITAL TV DRIVER
 M:	Daniel W. S. Almeida <dwlsalmeida@gmail.com>
 L:	linux-media@vger.kernel.org
diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig
index 6abc9302cd84..f2f4b2ec9135 100644
--- a/drivers/media/Kconfig
+++ b/drivers/media/Kconfig
@@ -239,6 +239,19 @@ source "drivers/media/firewire/Kconfig"
 # Common driver options
 source "drivers/media/common/Kconfig"
 
+config VCAM
+	tristate "V4L2 virtual camera"
+	depends on VIDEO_DEV
+	default m
+	select VIDEOBUF2_VMALLOC
+	help
+	  Say Y here to enable a DMA-BUF backed virtual camera driver capable
+	  of creating video capture devices to which data can be streamed
+	  through /dev/vcam after calling VCAM_IOC_CREATE. Frames are pushed
+	  with VCAM_IOC_QUEUE and recycled with VCAM_IOC_DEQUEUE.
+
+	  When in doubt, say N.
+
 endmenu
 
 #
diff --git a/drivers/media/Makefile b/drivers/media/Makefile
index 20fac24e4f0f..d539fecbe498 100644
--- a/drivers/media/Makefile
+++ b/drivers/media/Makefile
@@ -32,3 +32,4 @@ obj-$(CONFIG_CEC_CORE) += cec/
 obj-y += common/ platform/ pci/ usb/ mmc/ firewire/ spi/ test-drivers/
 obj-$(CONFIG_VIDEO_DEV) += radio/
 
+obj-$(CONFIG_VCAM) += vcam.o
diff --git a/drivers/media/vcam.c b/drivers/media/vcam.c
new file mode 100644
index 000000000000..787e2585e12c
--- /dev/null
+++ b/drivers/media/vcam.c
@@ -0,0 +1,1935 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Jarkko Sakkinen 2025-2026
+ *
+ * Derived originally from v4l2loopback driver but is essentially a rewrite.
+ */
+
+/**
+ * DOC: Theory of Operation
+ *
+ * The driver exposes /dev/vcam for creating virtual capture devices via
+ * %VCAM_IOC_CREATE. The ioctl registers a video capture node, stores the
+ * allowed capture modes provided by the caller, and associates output buffers
+ * described by &struct vcam_frame with DMA-BUF file descriptors supplied by
+ * the caller. This also keeps output buffers owned by the caller, and
+ * accounted from the calling process.
+ *
+ * Frames are pushed to the capture device by queueing output buffers using
+ * %VCAM_IOC_QUEUE, and recycling them with %VCAM_IOC_DEQUEUE. Queueing without
+ * dequeuing eventually exhausts the output queue and stalls the producer.
+ *
+ * If both buffers reference the same DMA-BUF, the driver performs a zero-copy
+ * transfer by propagating metadata. Otherwise, if both buffers are mappable,
+ * the payload is copied into the capture buffer. When neither zero-copy nor a
+ * CPU mapping is possible, the capture buffer completes with an error.
+ */
+
+#include <linux/cleanup.h>
+#include <linux/bitops.h>
+#include <linux/atomic.h>
+#include <linux/ctype.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/limits.h>
+#include <linux/device.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/miscdevice.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/time.h>
+#include <linux/math64.h>
+#include <linux/minmax.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/sort.h>
+#include <linux/spinlock.h>
+#include <linux/sysfs.h>
+#include <linux/videodev2.h>
+#include <linux/wait.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-vmalloc.h>
+#include <uapi/linux/vcam.h>
+
+#undef pr_fmt
+#define pr_fmt(fmt) "vcam: " fmt
+
+MODULE_DESCRIPTION("V4L2 virtual camera driver");
+MODULE_LICENSE("GPL");
+
+#define VCAM_CARD_LABEL_MAX sizeof_field(struct video_device, name)
+#define VCAM_FPS_MIN 1
+#define VCAM_FPS_MAX 1000
+
+#define VCAM_MIN_WIDTH 2
+#define VCAM_MIN_HEIGHT 2
+#define VCAM_MAX_WIDTH 8192
+#define VCAM_MAX_HEIGHT 8192
+#define VCAM_DEFAULT_WIDTH 640
+#define VCAM_DEFAULT_HEIGHT 480
+
+#define VCAM_MAX_FORMATS 16
+#define VCAM_MAX_MODES 64
+#define VCAM_MIN_FRAMES 2
+#define VCAM_MAX_FRAMES 32
+
+#define VCAM_STATUS_MASK (VCAM_STATUS_IDLE | VCAM_STATUS_STREAMING)
+
+enum vcam_flags {
+	VCAM_FLAG_IS_OPEN = 0x01,
+	VCAM_FLAG_CREATING = 0x02,
+	VCAM_FLAG_READY = 0x04,
+};
+
+struct vcam_buf {
+	struct vb2_v4l2_buffer vb;
+	struct list_head list;
+	unsigned long flags;
+};
+
+enum vcam_buf_flags {
+	VCAM_BUF_FLAG_MAPPABLE = BIT(0),
+};
+
+struct vcam {
+	unsigned long flags;
+	int device_nr;
+	struct v4l2_device v4l2_dev;
+	struct video_device *vdev;
+	struct vb2_queue capture_queue;
+	struct vb2_queue output_queue;
+	struct v4l2_pix_format pix_format;
+	struct vcam_mode *modes;
+	u32 nr_modes;
+	struct v4l2_captureparm capture;
+	atomic_t sequence;
+	struct list_head capture_list;
+	struct list_head output_list;
+	u64 status;
+	wait_queue_head_t status_waitq;
+	enum vb2_memory output_memory;
+
+	/* Protects status flags and wait queue updates. */
+	spinlock_t status_lock;
+
+	/* Shared lock for vdev and VB2 queues. */
+	struct mutex lock;
+
+	/* Protects capture_list and output_list. */
+	spinlock_t frame_lock;
+
+	/*
+	 * Maintains a shared reference between processes having either
+	 * /dev/vcam or /dev/videoX open.
+	 */
+	struct kref ref;
+};
+
+enum vcam_format_flags {
+	VCAM_PLANAR = BIT(0),
+	VCAM_COMPRESSED = BIT(1),
+};
+
+struct vcam_format {
+	int fourcc;
+	int depth;
+	int flags;
+};
+
+const struct vcam_format vcam_formats[] = {
+	{
+		.fourcc = V4L2_PIX_FMT_YUYV,
+		.depth = 16,
+		.flags = 0,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_NV12,
+		.depth = 12,
+		.flags = VCAM_PLANAR,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_MJPEG,
+		.depth = 32,
+		.flags = VCAM_COMPRESSED,
+	},
+};
+
+#define VCAM_NR_FORMATS ARRAY_SIZE(vcam_formats)
+
+static const struct vcam_format *vcam_find_format(int fourcc)
+{
+	unsigned int i;
+
+	for (i = 0; i < VCAM_NR_FORMATS; i++) {
+		if (vcam_formats[i].fourcc == fourcc)
+			return vcam_formats + i;
+	}
+
+	return NULL;
+}
+
+static void vcam_fmt_descr(char *dst, size_t dst_len, u32 format)
+{
+	snprintf(dst, dst_len, "[%c%c%c%c]", (format >> 0) & 0xFF,
+		 (format >> 8) & 0xFF, (format >> 16) & 0xFF,
+		 (format >> 24) & 0xFF);
+}
+
+static void vcam_fourcc_str(char *dst, u32 format)
+{
+	dst[0] = (format >> 0) & 0xFF;
+	dst[1] = (format >> 8) & 0xFF;
+	dst[2] = (format >> 16) & 0xFF;
+	dst[3] = (format >> 24) & 0xFF;
+	dst[4] = '\0';
+}
+
+static inline bool vcam_is_streaming(struct vcam *data)
+{
+	return vb2_is_streaming(&data->output_queue) ||
+	       vb2_is_streaming(&data->capture_queue);
+}
+
+static bool vcam_status_mask_ready(struct vcam *dev, u64 mask)
+{
+	unsigned long flags;
+	bool ready;
+
+	spin_lock_irqsave(&dev->status_lock, flags);
+	ready = (dev->status & mask) == mask;
+	spin_unlock_irqrestore(&dev->status_lock, flags);
+
+	return ready;
+}
+
+static void vcam_status_update_stream(struct vcam *dev, bool on)
+{
+	unsigned long flags;
+	u64 old_flags;
+	u64 new_flags;
+
+	spin_lock_irqsave(&dev->status_lock, flags);
+	old_flags = dev->status;
+	if (on) {
+		dev->status &= ~VCAM_STATUS_IDLE;
+		dev->status |= VCAM_STATUS_STREAMING;
+	} else {
+		dev->status &= ~VCAM_STATUS_STREAMING;
+		dev->status |= VCAM_STATUS_IDLE;
+	}
+	new_flags = dev->status;
+	spin_unlock_irqrestore(&dev->status_lock, flags);
+
+	if (new_flags != old_flags)
+		wake_up_interruptible(&dev->status_waitq);
+}
+
+static u64 vcam_status_read(struct vcam *dev)
+{
+	unsigned long flags;
+	u64 flags_snapshot;
+
+	spin_lock_irqsave(&dev->status_lock, flags);
+	flags_snapshot = dev->status;
+	spin_unlock_irqrestore(&dev->status_lock, flags);
+
+	return flags_snapshot;
+}
+
+static bool vcam_tpf_valid(const struct v4l2_fract *tpf)
+{
+	u64 min_den = (u64)tpf->numerator * VCAM_FPS_MIN;
+	u64 max_den = (u64)tpf->numerator * VCAM_FPS_MAX;
+
+	if (!tpf->numerator || !tpf->denominator)
+		return false;
+	if ((u64)tpf->denominator < min_den)
+		return false;
+	if ((u64)tpf->denominator > max_den)
+		return false;
+
+	return true;
+}
+
+static bool vcam_pix_format_eq(const struct v4l2_pix_format *src,
+			       const struct v4l2_pix_format *dest)
+{
+	return src->width == dest->width && src->height == dest->height &&
+	       src->pixelformat == dest->pixelformat;
+}
+
+static bool vcam_mode_has_pixelformat(const struct vcam *dev, u32 pixelformat)
+{
+	u32 i;
+
+	for (i = 0; i < dev->nr_modes; i++) {
+		if (dev->modes[i].pixelformat == pixelformat)
+			return true;
+	}
+
+	return false;
+}
+
+static bool vcam_mode_pixelformat_seen(const struct vcam *dev, u32 index,
+				       u32 pixelformat)
+{
+	u32 i;
+
+	for (i = 0; i < index; i++) {
+		if (dev->modes[i].pixelformat == pixelformat)
+			return true;
+	}
+
+	return false;
+}
+
+static bool vcam_mode_framesize_seen(const struct vcam *dev, u32 index,
+				     u32 pixelformat, u32 width, u32 height)
+{
+	u32 i;
+
+	for (i = 0; i < index; i++) {
+		const struct vcam_mode *mode = &dev->modes[i];
+
+		if (mode->pixelformat == pixelformat && mode->width == width &&
+		    mode->height == height)
+			return true;
+	}
+
+	return false;
+}
+
+static int vcam_mode_enum_format(const struct vcam *dev, u32 index,
+				 u32 *pixelformat)
+{
+	u32 i;
+	u32 match = 0;
+
+	for (i = 0; i < dev->nr_modes; i++) {
+		u32 fmt = dev->modes[i].pixelformat;
+
+		if (vcam_mode_pixelformat_seen(dev, i, fmt))
+			continue;
+
+		if (match++ == index) {
+			*pixelformat = fmt;
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static int vcam_mode_enum_framesize(const struct vcam *dev, u32 pixelformat,
+				    u32 index, u32 *width, u32 *height)
+{
+	u32 i;
+	u32 match = 0;
+
+	for (i = 0; i < dev->nr_modes; i++) {
+		const struct vcam_mode *mode = &dev->modes[i];
+
+		if (mode->pixelformat != pixelformat)
+			continue;
+		if (vcam_mode_framesize_seen(dev, i, pixelformat, mode->width,
+					     mode->height))
+			continue;
+		if (match++ == index) {
+			*width = mode->width;
+			*height = mode->height;
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static bool vcam_mode_has_framesize(const struct vcam *dev, u32 pixelformat,
+				    u32 width, u32 height)
+{
+	u32 i;
+
+	for (i = 0; i < dev->nr_modes; i++) {
+		const struct vcam_mode *mode = &dev->modes[i];
+
+		if (mode->pixelformat == pixelformat && mode->width == width &&
+		    mode->height == height)
+			return true;
+	}
+
+	return false;
+}
+
+static bool vcam_mode_matches_pix(const struct vcam_mode *mode,
+				  const struct v4l2_pix_format *pix)
+{
+	return mode->width == pix->width && mode->height == pix->height &&
+	       mode->pixelformat == pix->pixelformat &&
+	       mode->colorspace == pix->colorspace &&
+	       mode->stride == pix->bytesperline;
+}
+
+static int vcam_mode_cmp(const void *lhs, const void *rhs)
+{
+	const struct vcam_mode *a = lhs;
+	const struct vcam_mode *b = rhs;
+
+	if (a->pixelformat != b->pixelformat)
+		return (a->pixelformat > b->pixelformat) -
+		       (a->pixelformat < b->pixelformat);
+	if (a->width != b->width)
+		return (a->width > b->width) - (a->width < b->width);
+	if (a->height != b->height)
+		return (a->height > b->height) - (a->height < b->height);
+	if (a->colorspace != b->colorspace)
+		return (a->colorspace > b->colorspace) -
+		       (a->colorspace < b->colorspace);
+	return (a->stride > b->stride) - (a->stride < b->stride);
+}
+
+static bool vcam_mode_equal(const struct vcam_mode *a,
+			    const struct vcam_mode *b)
+{
+	return a->width == b->width && a->height == b->height &&
+	       a->pixelformat == b->pixelformat &&
+	       a->colorspace == b->colorspace && a->stride == b->stride;
+}
+
+static bool vcam_mode_allowed(const struct vcam *dev,
+			      const struct v4l2_pix_format *pix)
+{
+	u32 i;
+
+	for (i = 0; i < dev->nr_modes; i++) {
+		if (vcam_mode_matches_pix(&dev->modes[i], pix))
+			return true;
+	}
+
+	return false;
+}
+
+static int vcam_set_format(struct v4l2_format *fmt)
+{
+	struct v4l2_pix_format *pix = &fmt->fmt.pix;
+	const struct vcam_format *format;
+	u64 bytesperline;
+	u64 sizeimage;
+
+	if (V4L2_TYPE_IS_MULTIPLANAR(fmt->type))
+		return -EINVAL;
+
+	if (!pix->width)
+		pix->width = VCAM_DEFAULT_WIDTH;
+	if (!pix->height)
+		pix->height = VCAM_DEFAULT_HEIGHT;
+
+	pix->width = clamp(pix->width, VCAM_MIN_WIDTH, VCAM_MAX_WIDTH);
+	pix->height = clamp(pix->height, VCAM_MIN_HEIGHT, VCAM_MAX_HEIGHT);
+
+	format = vcam_find_format(pix->pixelformat);
+	if (!format) {
+		format = &vcam_formats[0];
+		pix->pixelformat = format->fourcc;
+	}
+
+	if (format->flags & VCAM_PLANAR) {
+		pix->bytesperline = pix->width;
+		sizeimage = ((u64)pix->width * pix->height * format->depth) >>
+			    3;
+	} else if (format->flags & VCAM_COMPRESSED) {
+		pix->bytesperline = 0;
+		sizeimage = ((u64)pix->width * pix->height * format->depth) >>
+			    3;
+	} else {
+		bytesperline = ((u64)pix->width * format->depth) >> 3;
+		if (bytesperline > U32_MAX)
+			return -EOVERFLOW;
+
+		pix->bytesperline = bytesperline;
+		sizeimage = (u64)pix->height * bytesperline;
+	}
+
+	if (sizeimage > U32_MAX)
+		return -EOVERFLOW;
+
+	pix->sizeimage = sizeimage;
+
+	if (pix->colorspace == V4L2_COLORSPACE_DEFAULT ||
+	    pix->colorspace > V4L2_COLORSPACE_DCI_P3)
+		pix->colorspace = V4L2_COLORSPACE_SRGB;
+	if (pix->field == V4L2_FIELD_ANY)
+		pix->field = V4L2_FIELD_NONE;
+
+	return 0;
+}
+
+static int vcam_vidioc_querycap(struct file *file, void *priv,
+				struct v4l2_capability *cap)
+{
+	__u32 capabilities = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE;
+	struct vcam *dev = video_drvdata(file);
+
+	cap->device_caps = capabilities;
+	cap->capabilities = capabilities | V4L2_CAP_DEVICE_CAPS;
+
+	strscpy(cap->driver, "vcam", sizeof(cap->driver));
+	strscpy(cap->card, dev->vdev->name, sizeof(cap->card));
+	snprintf(cap->bus_info, sizeof(cap->bus_info), "vcam:%d",
+		 dev->device_nr);
+
+	return 0;
+}
+
+static int vcam_enum_framesizes(struct vcam *dev, struct v4l2_frmsizeenum *argp)
+{
+	if (vcam_is_streaming(dev)) {
+		if (argp->index)
+			return -EINVAL;
+		if (argp->pixel_format != dev->pix_format.pixelformat)
+			return -EINVAL;
+
+		argp->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+
+		argp->discrete.width = dev->pix_format.width;
+		argp->discrete.height = dev->pix_format.height;
+	} else {
+		u32 width;
+		u32 height;
+
+		if (!vcam_find_format(argp->pixel_format) ||
+		    !vcam_mode_has_pixelformat(dev, argp->pixel_format))
+			return -EINVAL;
+
+		if (vcam_mode_enum_framesize(dev, argp->pixel_format,
+					     argp->index, &width, &height))
+			return -EINVAL;
+
+		argp->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+		argp->discrete.width = width;
+		argp->discrete.height = height;
+	}
+
+	return 0;
+}
+
+static int vcam_enum_frameintervals(struct vcam *dev,
+				    struct v4l2_frmivalenum *argp)
+{
+	if (vcam_is_streaming(dev)) {
+		if (argp->index)
+			return -EINVAL;
+		if (argp->width != dev->pix_format.width ||
+		    argp->height != dev->pix_format.height ||
+		    argp->pixel_format != dev->pix_format.pixelformat)
+			return -EINVAL;
+
+		argp->type = V4L2_FRMIVAL_TYPE_DISCRETE;
+		argp->discrete = dev->capture.timeperframe;
+	} else {
+		if (!vcam_find_format(argp->pixel_format) ||
+		    !vcam_mode_has_framesize(dev, argp->pixel_format,
+					     argp->width, argp->height))
+			return -EINVAL;
+
+		if (argp->index)
+			return -EINVAL;
+
+		argp->type = V4L2_FRMIVAL_TYPE_CONTINUOUS;
+		argp->stepwise.min.numerator = 1;
+		argp->stepwise.min.denominator = VCAM_FPS_MAX;
+		argp->stepwise.max.numerator = 1;
+		argp->stepwise.max.denominator = VCAM_FPS_MIN;
+		argp->stepwise.step.numerator = 1;
+		argp->stepwise.step.denominator = 1;
+	}
+
+	return 0;
+}
+
+static int vcam_vidioc_enum_framesizes(struct file *file, void *fh,
+				       struct v4l2_frmsizeenum *argp)
+{
+	struct vcam *dev = video_drvdata(file);
+
+	return vcam_enum_framesizes(dev, argp);
+}
+
+static int vcam_vidioc_enum_frameintervals(struct file *file, void *fh,
+					   struct v4l2_frmivalenum *argp)
+{
+	struct vcam *dev = video_drvdata(file);
+
+	return vcam_enum_frameintervals(dev, argp);
+}
+
+static int vcam_vidioc_enum_fmt_cap(struct file *file, void *fh,
+				    struct v4l2_fmtdesc *f)
+{
+	struct vcam *dev;
+
+	dev = video_drvdata(file);
+
+	if (vcam_is_streaming(dev)) {
+		const __u32 format = dev->pix_format.pixelformat;
+
+		if (f->index)
+			return -EINVAL;
+
+		f->pixelformat = dev->pix_format.pixelformat;
+		vcam_fmt_descr(f->description, sizeof(f->description), format);
+	} else {
+		u32 pixelformat;
+
+		if (vcam_mode_enum_format(dev, f->index, &pixelformat))
+			return -EINVAL;
+
+		f->pixelformat = pixelformat;
+		vcam_fmt_descr(f->description, sizeof(f->description),
+			       pixelformat);
+	}
+	f->flags = 0;
+	return 0;
+}
+
+static int vcam_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+				     struct v4l2_format *fmt)
+{
+	struct vcam *dev;
+
+	dev = video_drvdata(file);
+
+	fmt->fmt.pix = dev->pix_format;
+	return 0;
+}
+
+static int vcam_vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+				       struct v4l2_format *fmt)
+{
+	struct vcam *dev = video_drvdata(file);
+	struct v4l2_format try_fmt;
+	int ret;
+
+	if (!V4L2_TYPE_IS_CAPTURE(fmt->type))
+		return -EINVAL;
+
+	if (vcam_is_streaming(dev)) {
+		if (!vcam_pix_format_eq(&dev->pix_format, &fmt->fmt.pix))
+			return -EBUSY;
+
+		fmt->fmt.pix = dev->pix_format;
+	}
+
+	try_fmt = *fmt;
+	ret = vcam_set_format(&try_fmt);
+	if (ret)
+		return ret;
+	if (!vcam_mode_allowed(dev, &try_fmt.fmt.pix))
+		return -EINVAL;
+	*fmt = try_fmt;
+	return 0;
+}
+
+static int vcam_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+				     struct v4l2_format *fmt)
+{
+	struct vcam *dev = video_drvdata(file);
+	struct v4l2_format try_fmt = *fmt;
+	int ret;
+
+	if (!V4L2_TYPE_IS_CAPTURE(fmt->type))
+		return -EINVAL;
+
+	if (vcam_is_streaming(dev)) {
+		if (!vcam_pix_format_eq(&dev->pix_format, &fmt->fmt.pix))
+			return -EBUSY;
+
+		fmt->fmt.pix = dev->pix_format;
+	}
+
+	ret = vcam_set_format(&try_fmt);
+	if (ret)
+		return ret;
+
+	if (!vcam_mode_allowed(dev, &try_fmt.fmt.pix))
+		return -EINVAL;
+
+	if (vb2_is_busy(&dev->output_queue) &&
+	    !vcam_pix_format_eq(&dev->pix_format, &try_fmt.fmt.pix))
+		return -EBUSY;
+
+	dev->pix_format = try_fmt.fmt.pix;
+	*fmt = try_fmt;
+	return 0;
+}
+
+static int vcam_ioc_reqbufs(struct file *file, struct vcam *dev,
+			    struct v4l2_requestbuffers *req)
+{
+	int ret = 0;
+
+	if (req->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+		return -EINVAL;
+
+	scoped_guard(mutex, &dev->lock)
+	{
+		if (vb2_queue_is_busy(&dev->output_queue, file)) {
+			ret = -EBUSY;
+			break;
+		}
+
+		ret = vb2_reqbufs(&dev->output_queue, req);
+		if (!ret)
+			dev->output_queue.owner =
+				req->count ? file->private_data : NULL;
+	}
+	return ret;
+}
+
+static int vcam_ioc_querybuf(struct file *file, struct vcam *dev,
+			     struct v4l2_buffer *buf)
+{
+	int ret = 0;
+
+	if (buf->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+		return -EINVAL;
+
+	scoped_guard(mutex, &dev->lock)
+		ret = vb2_querybuf(&dev->output_queue, buf);
+
+	return ret;
+}
+
+static ssize_t formats_show(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct vcam_format_entry {
+		u32 fourcc;
+		char name[5];
+	};
+	struct vcam_format_entry formats[VCAM_MAX_FORMATS];
+	struct vcam_format_entry tmp;
+	unsigned int count =
+		min_t(unsigned int, VCAM_NR_FORMATS, VCAM_MAX_FORMATS);
+	size_t len = 0;
+	unsigned int i, j;
+
+	for (i = 0; i < count; i++) {
+		formats[i].fourcc = vcam_formats[i].fourcc;
+		vcam_fourcc_str(formats[i].name, formats[i].fourcc);
+	}
+
+	for (i = 1; i < count; i++) {
+		for (j = i; j > 0; j--) {
+			if (strcmp(formats[j - 1].name, formats[j].name) <= 0)
+				break;
+			tmp = formats[j - 1];
+			formats[j - 1] = formats[j];
+			formats[j] = tmp;
+		}
+	}
+
+	for (i = 0; i < count; i++)
+		len += sysfs_emit_at(buf, len, "%s%s", i ? " " : "",
+				     formats[i].name);
+
+	len += sysfs_emit_at(buf, len, "\n");
+	return len;
+}
+
+static ssize_t max_width_show(struct device *dev, struct device_attribute *attr,
+			      char *buf)
+{
+	return sysfs_emit(buf, "%u\n", VCAM_MAX_WIDTH);
+}
+
+static ssize_t max_height_show(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "%u\n", VCAM_MAX_HEIGHT);
+}
+
+static ssize_t max_frames_show(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "%u\n", VCAM_MAX_FRAMES);
+}
+
+static DEVICE_ATTR_RO(formats);
+static DEVICE_ATTR_RO(max_frames);
+static DEVICE_ATTR_RO(max_height);
+static DEVICE_ATTR_RO(max_width);
+
+static struct attribute *vcam_attrs[] = {
+	&dev_attr_formats.attr,
+	&dev_attr_max_frames.attr,
+	&dev_attr_max_height.attr,
+	&dev_attr_max_width.attr,
+	NULL,
+};
+
+static const struct attribute_group vcam_attr_group = {
+	.attrs = vcam_attrs,
+};
+
+static const struct attribute_group *vcam_attr_groups[] = {
+	&vcam_attr_group,
+	NULL,
+};
+
+static int vcam_ioc_alloc(struct file *file, struct vcam *dev, u32 nr_frames,
+			  void __user *frames_user, enum vb2_memory memory)
+{
+	struct v4l2_requestbuffers req = {
+		.type = V4L2_BUF_TYPE_VIDEO_OUTPUT,
+		.memory = memory,
+	};
+	struct v4l2_buffer buf;
+	struct vcam_frame *frames = NULL;
+	unsigned int i;
+	int ret;
+
+	if (memory == VB2_MEMORY_DMABUF &&
+	    !dev->output_queue.mem_ops->attach_dmabuf)
+		return -EOPNOTSUPP;
+
+	if (!frames_user)
+		return -EINVAL;
+
+	if (nr_frames) {
+		frames = kcalloc(nr_frames, sizeof(*frames), GFP_KERNEL);
+		if (!frames)
+			return -ENOMEM;
+	}
+
+	if (copy_from_user(frames, frames_user, nr_frames * sizeof(*frames))) {
+		ret = -EFAULT;
+		goto out_free;
+	}
+
+	req.count = nr_frames;
+	ret = vcam_ioc_reqbufs(file, dev, &req);
+	if (ret)
+		goto out_free;
+
+	if (req.count != nr_frames) {
+		struct v4l2_requestbuffers req_free = {
+			.type = V4L2_BUF_TYPE_VIDEO_OUTPUT,
+			.memory = memory,
+			.count = 0,
+		};
+
+		vcam_ioc_reqbufs(file, dev, &req_free);
+		ret = -ENOMEM;
+		goto out_free;
+	}
+
+	dev->output_memory = memory;
+
+	for (i = 0; i < nr_frames; i++) {
+		memset(&buf, 0, sizeof(buf));
+		buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+		buf.memory = memory;
+		buf.index = i;
+
+		ret = vcam_ioc_querybuf(file, dev, &buf);
+		if (ret)
+			goto out_free_reqbufs;
+
+		frames[i].index = i;
+		frames[i].length = buf.length;
+	}
+
+	if (copy_to_user(frames_user, frames, nr_frames * sizeof(*frames)))
+		ret = -EFAULT;
+
+out_free_reqbufs:
+	if (ret) {
+		struct v4l2_requestbuffers req_free = {
+			.type = V4L2_BUF_TYPE_VIDEO_OUTPUT,
+			.memory = memory,
+			.count = 0,
+		};
+
+		vcam_ioc_reqbufs(file, dev, &req_free);
+		dev->output_memory = VB2_MEMORY_DMABUF;
+	}
+out_free:
+	kfree(frames);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int vcam_ioc_queue(struct file *file, struct vcam *dev,
+			  struct vcam_ioc_queue *queue)
+{
+	struct v4l2_buffer buf = {
+		.type = V4L2_BUF_TYPE_VIDEO_OUTPUT,
+		.memory = dev->output_memory,
+		.index = queue->index,
+		.bytesused = queue->length,
+	};
+	u32 remainder;
+	int ret;
+
+	if (queue->reserved)
+		return -EINVAL;
+
+	if (dev->output_memory == VB2_MEMORY_DMABUF) {
+		buf.m.fd = queue->fd;
+		buf.length = dev->pix_format.sizeimage;
+	}
+
+	buf.timestamp.tv_sec =
+		div_u64_rem(queue->timestamp, NSEC_PER_SEC, &remainder);
+	buf.timestamp.tv_usec = remainder / NSEC_PER_USEC;
+
+	scoped_guard(mutex, &dev->lock)
+	{
+		if (vb2_queue_is_busy(&dev->output_queue, file)) {
+			ret = -EBUSY;
+			break;
+		}
+
+		if (vb2_is_streaming(&dev->capture_queue) &&
+		    !vb2_is_streaming(&dev->output_queue)) {
+			ret = vb2_streamon(&dev->output_queue, buf.type);
+			if (ret)
+				break;
+		}
+
+		ret = vb2_qbuf(&dev->output_queue, NULL, &buf);
+	}
+
+	return ret;
+}
+
+static int vcam_ioc_dequeue(struct file *file, struct vcam *dev,
+			    struct vcam_ioc_dequeue *queue)
+{
+	struct v4l2_buffer buf = {
+		.type = V4L2_BUF_TYPE_VIDEO_OUTPUT,
+		.memory = dev->output_memory,
+	};
+	int ret;
+
+	scoped_guard(mutex, &dev->lock)
+	{
+		if (vb2_queue_is_busy(&dev->output_queue, file)) {
+			ret = -EBUSY;
+			break;
+		}
+
+		ret = vb2_dqbuf(&dev->output_queue, &buf,
+				file->f_flags & O_NONBLOCK);
+	}
+	if (ret)
+		return ret;
+
+	queue->index = buf.index;
+	queue->length = buf.bytesused;
+	queue->timestamp = (u64)buf.timestamp.tv_sec * NSEC_PER_SEC +
+			   (u64)buf.timestamp.tv_usec * NSEC_PER_USEC;
+	return 0;
+}
+
+static int vcam_ioc_wait(struct vcam *dev, struct vcam_ioc_wait *wait)
+{
+	const struct v4l2_pix_format *pix = &dev->pix_format;
+	struct vcam_mode mode;
+	int ret;
+
+	if (wait->reserved)
+		return -EINVAL;
+
+	if (wait->mask & ~VCAM_STATUS_MASK)
+		return -EINVAL;
+
+	if (!wait->mode)
+		return -EINVAL;
+
+	if (wait->mask) {
+		ret = wait_event_interruptible(dev->status_waitq,
+					       vcam_status_mask_ready(dev,
+								      wait->mask));
+		if (ret)
+			return ret;
+	}
+
+	wait->status = vcam_status_read(dev);
+	mode = (struct vcam_mode){
+		.width = pix->width,
+		.height = pix->height,
+		.pixelformat = pix->pixelformat,
+		.colorspace = pix->colorspace,
+		.stride = pix->bytesperline,
+	};
+
+	if (copy_to_user(u64_to_user_ptr(wait->mode), &mode, sizeof(mode)))
+		return -EFAULT;
+	return 0;
+}
+
+static long vcam_output_ioctl_core(struct file *file, unsigned int cmd,
+				   void *arg)
+{
+	struct vcam *dev = file->private_data;
+	long ret = 0;
+
+	switch (cmd) {
+	case VCAM_IOC_QUEUE:
+		ret = vcam_ioc_queue(file, dev, arg);
+		break;
+	case VCAM_IOC_DEQUEUE:
+		ret = vcam_ioc_dequeue(file, dev, arg);
+		break;
+	case VCAM_IOC_WAIT:
+		ret = vcam_ioc_wait(dev, arg);
+		break;
+	default:
+		ret = -EOPNOTSUPP;
+		break;
+	}
+
+	return ret;
+}
+
+static long vcam_ioctl_common(struct file *file, unsigned int cmd,
+			      unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	void *karg;
+	size_t size;
+	long ret;
+
+	switch (cmd) {
+	case VCAM_IOC_QUEUE:
+		size = sizeof(struct vcam_ioc_queue);
+		break;
+	case VCAM_IOC_DEQUEUE:
+		size = sizeof(struct vcam_ioc_dequeue);
+		break;
+	case VCAM_IOC_WAIT:
+		size = sizeof(struct vcam_ioc_wait);
+		break;
+	default:
+		return -ENOTTY;
+	}
+
+	if (size > SZ_4K)
+		return -ENOTTY;
+
+	karg = kzalloc(size, GFP_KERNEL);
+	if (!karg)
+		return -ENOMEM;
+
+	if (copy_from_user(karg, argp, size)) {
+		ret = -EFAULT;
+		goto out_free;
+	}
+
+	ret = vcam_output_ioctl_core(file, cmd, karg);
+	if (ret)
+		goto out_free;
+
+	if (copy_to_user(argp, karg, size)) {
+		ret = -EFAULT;
+		goto out_free;
+	}
+
+	ret = 0;
+out_free:
+	kfree(karg);
+	return ret;
+}
+
+static void __vcam_release(struct vcam *dev)
+{
+	if (!dev->vdev)
+		return;
+
+	vb2_queue_release(&dev->output_queue);
+	vb2_queue_release(&dev->capture_queue);
+	kfree(dev->modes);
+	dev->modes = NULL;
+	dev->nr_modes = 0;
+
+	if (video_is_registered(dev->vdev))
+		video_unregister_device(dev->vdev);
+	else
+		video_device_release(dev->vdev);
+
+	v4l2_device_unregister(&dev->v4l2_dev);
+
+	dev->vdev = NULL;
+	dev->device_nr = -1;
+}
+
+static void vcam_release(struct kref *ref)
+{
+	struct vcam *dev;
+
+	dev = container_of(ref, struct vcam, ref);
+
+	if (!test_bit(VCAM_FLAG_CREATING, &dev->flags) || dev->device_nr < 0) {
+		kfree(dev);
+		return;
+	}
+
+	__vcam_release(dev);
+	kfree(dev);
+}
+
+static int __vcam_close(struct inode *inode, struct file *file)
+{
+	struct vcam *dev = file->private_data;
+
+	if (dev->vdev && video_is_registered(dev->vdev))
+		video_unregister_device(dev->vdev);
+
+	vb2_queue_release(&dev->output_queue);
+
+	dev->output_memory = VB2_MEMORY_DMABUF;
+	kfree(dev->modes);
+	dev->modes = NULL;
+	dev->nr_modes = 0;
+
+	kref_put(&dev->ref, vcam_release);
+	return 0;
+}
+
+static int vcam_open(struct inode *inode, struct file *file)
+{
+	struct vcam *dev;
+	int ret = nonseekable_open(inode, file);
+
+	if (ret)
+		return ret;
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+
+	kref_init(&dev->ref);
+	dev->device_nr = -1;
+	file->private_data = dev;
+	return 0;
+}
+
+static int vcam_close(struct inode *inode, struct file *file)
+{
+	struct vcam *dev = file->private_data;
+	int ret = 0;
+
+	if (!dev)
+		return 0;
+
+	if (test_bit(VCAM_FLAG_CREATING, &dev->flags) && dev->device_nr >= 0)
+		ret = __vcam_close(inode, file);
+	else
+		kref_put(&dev->ref, vcam_release);
+
+	file->private_data = NULL;
+	return ret;
+}
+
+static __poll_t vcam_poll(struct file *file, struct poll_table_struct *pts)
+{
+	struct vcam *dev = file->private_data;
+
+	if (!dev || !test_bit(VCAM_FLAG_CREATING, &dev->flags) ||
+	    !test_bit(VCAM_FLAG_READY, &dev->flags) || dev->device_nr < 0)
+		return POLLERR;
+
+	return vb2_core_poll(&dev->output_queue, file, pts);
+}
+
+static int vcam_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	struct vcam *dev = file->private_data;
+
+	if (!dev || !test_bit(VCAM_FLAG_CREATING, &dev->flags) ||
+	    !test_bit(VCAM_FLAG_READY, &dev->flags) || dev->device_nr < 0)
+		return -ENOTTY;
+
+	return vb2_mmap(&dev->output_queue, vma);
+}
+
+static int vcam_vidioc_g_parm(struct file *file, void *priv,
+			      struct v4l2_streamparm *parm)
+{
+	struct vcam *dev;
+
+	if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	dev = video_drvdata(file);
+	parm->parm.capture = dev->capture;
+	return 0;
+}
+
+static int vcam_vidioc_s_parm(struct file *file, void *priv,
+			      struct v4l2_streamparm *parm)
+{
+	struct v4l2_fract *tpf = &parm->parm.capture.timeperframe;
+	struct vcam *dev = video_drvdata(file);
+
+	if (!vcam_tpf_valid(tpf))
+		return -EINVAL;
+
+	if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	dev->capture.timeperframe = *tpf;
+	parm->parm.capture = dev->capture;
+	return 0;
+}
+
+static int vcam_vidioc_enum_input(struct file *file, void *fh,
+				  struct v4l2_input *inp)
+{
+	struct vcam *dev;
+	__u32 index = inp->index;
+
+	if (index != 0)
+		return -EINVAL;
+
+	memset(inp, 0, sizeof(*inp));
+
+	inp->index = index;
+	strscpy(inp->name, "vcam", sizeof(inp->name));
+	inp->type = V4L2_INPUT_TYPE_CAMERA;
+	inp->audioset = 0;
+	inp->tuner = 0;
+	inp->status = 0;
+
+	dev = video_drvdata(file);
+	if (!vb2_is_streaming(&dev->output_queue))
+		inp->status |= V4L2_IN_ST_NO_SIGNAL;
+
+	return 0;
+}
+
+static int vcam_vidioc_g_input(struct file *file, void *fh, unsigned int *i)
+{
+	*i = 0;
+	return 0;
+}
+
+static int vcam_vidioc_s_input(struct file *file, void *fh, unsigned int i)
+{
+	if (i == 0)
+		return 0;
+
+	return -EINVAL;
+}
+
+static int vcam_vidioc_streamon(struct file *file, void *fh,
+				enum v4l2_buf_type type)
+{
+	struct vcam *dev = video_drvdata(file);
+	int ret;
+
+	if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	if (vb2_queue_is_busy(&dev->capture_queue, file))
+		return -EBUSY;
+
+	ret = vb2_streamon(&dev->capture_queue, type);
+	if (ret)
+		return ret;
+
+	if (vb2_get_num_buffers(&dev->output_queue)) {
+		ret = vb2_streamon(&dev->output_queue,
+				   V4L2_BUF_TYPE_VIDEO_OUTPUT);
+		if (ret) {
+			vb2_streamoff(&dev->capture_queue, type);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int vcam_vidioc_streamoff(struct file *file, void *fh,
+				 enum v4l2_buf_type type)
+{
+	struct vcam *dev = video_drvdata(file);
+	int ret;
+
+	if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	if (vb2_queue_is_busy(&dev->capture_queue, file))
+		return -EBUSY;
+
+	ret = vb2_streamoff(&dev->capture_queue, type);
+	if (ret)
+		return ret;
+
+	if (vb2_get_num_buffers(&dev->output_queue))
+		vb2_streamoff(&dev->output_queue, V4L2_BUF_TYPE_VIDEO_OUTPUT);
+
+	return 0;
+}
+
+static const struct v4l2_ioctl_ops vcam_ioctl_ops = {
+	.vidioc_querycap = &vcam_vidioc_querycap,
+	.vidioc_enum_framesizes = &vcam_vidioc_enum_framesizes,
+	.vidioc_enum_frameintervals = &vcam_vidioc_enum_frameintervals,
+	.vidioc_enum_input = &vcam_vidioc_enum_input,
+	.vidioc_g_input = &vcam_vidioc_g_input,
+	.vidioc_s_input = &vcam_vidioc_s_input,
+	.vidioc_enum_fmt_vid_cap = &vcam_vidioc_enum_fmt_cap,
+	.vidioc_g_fmt_vid_cap = &vcam_vidioc_g_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap = &vcam_vidioc_s_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap = &vcam_vidioc_try_fmt_vid_cap,
+	.vidioc_g_parm = &vcam_vidioc_g_parm,
+	.vidioc_s_parm = &vcam_vidioc_s_parm,
+
+	.vidioc_reqbufs = &vb2_ioctl_reqbufs,
+	.vidioc_create_bufs = &vb2_ioctl_create_bufs,
+	.vidioc_prepare_buf = &vb2_ioctl_prepare_buf,
+	.vidioc_querybuf = &vb2_ioctl_querybuf,
+	.vidioc_qbuf = &vb2_ioctl_qbuf,
+	.vidioc_dqbuf = &vb2_ioctl_dqbuf,
+	.vidioc_expbuf = &vb2_ioctl_expbuf,
+	.vidioc_streamon = &vcam_vidioc_streamon,
+	.vidioc_streamoff = &vcam_vidioc_streamoff,
+};
+
+static enum vb2_buffer_state vcam_buf_fill(struct vcam *dev,
+					   struct vcam_buf *buf,
+					   const void *src, u32 src_len,
+					   u64 timestamp)
+{
+	struct vb2_buffer *vb = &buf->vb.vb2_buf;
+	u32 sequence;
+	void *dst;
+
+	dst = vb2_plane_vaddr(vb, 0);
+	if (!dst)
+		return VB2_BUF_STATE_ERROR;
+
+	if (!src_len || src_len > dev->pix_format.sizeimage)
+		src_len = dev->pix_format.sizeimage;
+
+	if (!src)
+		return VB2_BUF_STATE_ERROR;
+
+	memcpy(dst, src, src_len);
+
+	sequence = (u32)(atomic_inc_return(&dev->sequence) - 1);
+
+	vb->timestamp = timestamp ? timestamp : ktime_get_ns();
+	buf->vb.sequence = sequence;
+	buf->vb.field = dev->pix_format.field;
+	vb2_set_plane_payload(vb, 0, src_len);
+
+	return VB2_BUF_STATE_DONE;
+}
+
+static bool vcam_buf_flip(struct vcam *dev, struct vb2_buffer *out_vb,
+			  struct vcam_buf *cap_buf, u32 bytesused)
+{
+	struct vb2_buffer *cap_vb = &cap_buf->vb.vb2_buf;
+	u32 sequence;
+
+	if (!out_vb->planes[0].dbuf || !cap_vb->planes[0].dbuf)
+		return false;
+
+	if (out_vb->planes[0].dbuf != cap_vb->planes[0].dbuf)
+		return false;
+
+	if (!bytesused)
+		bytesused = dev->pix_format.sizeimage;
+	if (bytesused > vb2_plane_size(cap_vb, 0))
+		bytesused = vb2_plane_size(cap_vb, 0);
+
+	sequence = (u32)(atomic_inc_return(&dev->sequence) - 1);
+
+	cap_vb->timestamp = out_vb->timestamp ? out_vb->timestamp :
+						ktime_get_ns();
+	cap_buf->vb.sequence = sequence;
+	cap_buf->vb.field = dev->pix_format.field;
+	vb2_set_plane_payload(cap_vb, 0, bytesused);
+
+	return true;
+}
+
+static bool vcam_buf_pair_dequeue(struct vcam *dev, struct vcam_buf **out_buf,
+				  struct vcam_buf **cap_buf)
+{
+	unsigned long flags;
+	bool dequeued = false;
+
+	spin_lock_irqsave(&dev->frame_lock, flags);
+	if (!list_empty(&dev->output_list) && !list_empty(&dev->capture_list)) {
+		*out_buf = list_first_entry(&dev->output_list, struct vcam_buf,
+					    list);
+		list_del(&(*out_buf)->list);
+		*cap_buf = list_first_entry(&dev->capture_list, struct vcam_buf,
+					    list);
+		list_del(&(*cap_buf)->list);
+		dequeued = true;
+	}
+	spin_unlock_irqrestore(&dev->frame_lock, flags);
+	return dequeued;
+}
+
+static void vcam_dequeue_frames(struct vcam *data)
+{
+	const struct vcam_format *format;
+	enum vb2_buffer_state cap_state;
+	struct vcam_buf *cap_buf;
+	struct vcam_buf *out_buf;
+	struct vb2_buffer *vb;
+	bool zero_copy;
+	u32 bytesused;
+	void *src;
+
+	if (!vcam_is_streaming(data))
+		return;
+
+	format = vcam_find_format(data->pix_format.pixelformat);
+	while (vcam_buf_pair_dequeue(data, &out_buf, &cap_buf)) {
+		cap_state = VB2_BUF_STATE_DONE;
+		vb = &out_buf->vb.vb2_buf;
+		bytesused = vb2_get_plane_payload(vb, 0);
+		if (!bytesused || bytesused > data->pix_format.sizeimage)
+			bytesused = data->pix_format.sizeimage;
+
+		if (bytesused < data->pix_format.sizeimage &&
+		    (!format || !(format->flags & VCAM_COMPRESSED))) {
+			cap_state = VB2_BUF_STATE_ERROR;
+			goto out_done;
+		}
+
+		zero_copy = vcam_buf_flip(data, vb, cap_buf, bytesused);
+		if (!zero_copy &&
+		    (!(out_buf->flags & VCAM_BUF_FLAG_MAPPABLE) ||
+		     !(cap_buf->flags & VCAM_BUF_FLAG_MAPPABLE))) {
+			dev_dbg(&data->vdev->dev,
+				"unshared unmappable capture and output");
+			cap_state = VB2_BUF_STATE_ERROR;
+			goto out_done;
+		}
+		if (!zero_copy) {
+			src = vb2_plane_vaddr(vb, 0);
+			if (!src) {
+				cap_state = VB2_BUF_STATE_ERROR;
+				goto out_done;
+			}
+
+			cap_state = vcam_buf_fill(data, cap_buf, src, bytesused,
+						  vb->timestamp);
+		}
+out_done:
+		vb2_buffer_done(&cap_buf->vb.vb2_buf, cap_state);
+
+		if (cap_state == VB2_BUF_STATE_ERROR)
+			vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
+		else
+			vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
+	}
+}
+
+static int vcam_vdev_open(struct file *file)
+{
+	struct vcam *dev;
+	int ret;
+
+	dev = video_drvdata(file);
+	if (test_and_set_bit(VCAM_FLAG_IS_OPEN, &dev->flags))
+		return -EBUSY;
+	if (dev->device_nr < 0 || !test_bit(VCAM_FLAG_READY, &dev->flags)) {
+		clear_bit(VCAM_FLAG_IS_OPEN, &dev->flags);
+		return -ENODEV;
+	}
+
+	ret = v4l2_fh_open(file);
+	if (ret) {
+		clear_bit(VCAM_FLAG_IS_OPEN, &dev->flags);
+		return ret;
+	}
+
+	kref_get(&dev->ref);
+	return 0;
+}
+
+static int vcam_vdev_close(struct file *file)
+{
+	struct vcam *dev;
+	int ret;
+
+	dev = video_drvdata(file);
+	ret = _vb2_fop_release(file, NULL);
+	clear_bit(VCAM_FLAG_IS_OPEN, &dev->flags);
+
+	kref_put(&dev->ref, vcam_release);
+	return ret;
+}
+
+static const struct v4l2_file_operations vcam_vdev_fops = {
+	.owner = THIS_MODULE,
+	.open = vcam_vdev_open,
+	.release = vcam_vdev_close,
+	.poll = vb2_fop_poll,
+	.mmap = vb2_fop_mmap,
+	.unlocked_ioctl = video_ioctl2,
+};
+
+static int vcam_ioc_create_validate(struct vcam_ioc_create *config,
+				    char *card_label)
+{
+	long len, i;
+
+	if (config->device_nr != 0)
+		return -EINVAL;
+	if (config->reserved)
+		return -EINVAL;
+	if (!config->nr_modes || config->nr_modes > VCAM_MAX_MODES)
+		return -EINVAL;
+	if (!config->modes)
+		return -EINVAL;
+	if (config->nr_frames > VCAM_MAX_FRAMES)
+		return -E2BIG;
+	if (config->nr_frames < VCAM_MIN_FRAMES)
+		return -EINVAL;
+	if (!config->frames)
+		return -EINVAL;
+
+	memset(card_label, 0, VCAM_CARD_LABEL_MAX);
+	len = strncpy_from_user(card_label,
+				u64_to_user_ptr(config->device_name),
+				VCAM_CARD_LABEL_MAX);
+	if (len < 0)
+		return -EFAULT;
+	if (len >= VCAM_CARD_LABEL_MAX)
+		return -E2BIG;
+	if (!len)
+		return -EINVAL;
+	if (!isalnum((unsigned char)card_label[0]))
+		return -EINVAL;
+	for (i = 0; i < len; i++) {
+		if (!isalnum((unsigned char)card_label[i]) &&
+		    !isspace((unsigned char)card_label[i]))
+			return -EINVAL;
+	}
+	if (!isalnum((unsigned char)card_label[len - 1]))
+		return -EINVAL;
+
+	return len;
+}
+
+static int vcam_vb2_queue_setup(struct vb2_queue *queue,
+				unsigned int *nr_buffers,
+				unsigned int *nr_planes, unsigned int sizes[],
+				struct device *alloc_devs[])
+{
+	struct vcam *data = vb2_get_drv_priv(queue);
+	unsigned int sizeimage = data->pix_format.sizeimage;
+
+	if (!sizeimage)
+		return -EINVAL;
+
+	if (*nr_buffers < VCAM_MIN_FRAMES)
+		*nr_buffers = VCAM_MIN_FRAMES;
+	if (*nr_buffers > VCAM_MAX_FRAMES)
+		*nr_buffers = VCAM_MAX_FRAMES;
+
+	if (*nr_planes)
+		return sizes[0] < sizeimage ? -EINVAL : 0;
+
+	*nr_planes = 1;
+	sizes[0] = sizeimage;
+	return 0;
+}
+
+static int vcam_vb2_buf_prepare(struct vb2_buffer *vb)
+{
+	struct vcam *data = vb2_get_drv_priv(vb->vb2_queue);
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct vcam_buf *buf = container_of(vbuf, struct vcam_buf, vb);
+	unsigned int sizeimage = data->pix_format.sizeimage;
+	unsigned int bytesused;
+	void *vaddr;
+
+	if (vb2_plane_size(vb, 0) < sizeimage)
+		return -EINVAL;
+
+	vbuf->field = data->pix_format.field;
+	bytesused = vb2_get_plane_payload(vb, 0);
+	if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type) && !bytesused)
+		vb2_set_plane_payload(vb, 0, sizeimage);
+
+	buf->flags = VCAM_BUF_FLAG_MAPPABLE;
+	if (vb->planes[0].dbuf) {
+		vaddr = vb2_plane_vaddr(vb, 0);
+		if (!vaddr)
+			buf->flags &= ~VCAM_BUF_FLAG_MAPPABLE;
+	}
+	return 0;
+}
+
+static void vcam_vb2_buf_queue(struct vb2_buffer *vb)
+{
+	struct vcam *data = vb2_get_drv_priv(vb->vb2_queue);
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct vcam_buf *buf;
+	unsigned long flags;
+
+	buf = container_of(vbuf, struct vcam_buf, vb);
+
+	if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) {
+		spin_lock_irqsave(&data->frame_lock, flags);
+		list_add_tail(&buf->list, &data->output_list);
+		spin_unlock_irqrestore(&data->frame_lock, flags);
+	} else {
+		spin_lock_irqsave(&data->frame_lock, flags);
+		list_add_tail(&buf->list, &data->capture_list);
+		spin_unlock_irqrestore(&data->frame_lock, flags);
+	}
+
+	vcam_dequeue_frames(data);
+}
+
+static int vcam_vb2_prepare_streaming(struct vb2_queue *vq)
+{
+	return 0;
+}
+
+static int vcam_vb2_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct vcam *data = vb2_get_drv_priv(vq);
+
+	if (V4L2_TYPE_IS_CAPTURE(vq->type)) {
+		atomic_set(&data->sequence, 0);
+		vcam_status_update_stream(data, true);
+	}
+
+	vcam_dequeue_frames(data);
+	return 0;
+}
+
+static void vcam_vb2_stop_streaming(struct vb2_queue *vq)
+{
+	struct vcam *data = vb2_get_drv_priv(vq);
+	struct vcam_buf *buf, *tmp;
+	unsigned long flags;
+	LIST_HEAD(done_list);
+
+	if (V4L2_TYPE_IS_CAPTURE(vq->type))
+		vcam_status_update_stream(data, false);
+
+	spin_lock_irqsave(&data->frame_lock, flags);
+	list_splice_init(&data->output_list, &done_list);
+	list_splice_init(&data->capture_list, &done_list);
+	spin_unlock_irqrestore(&data->frame_lock, flags);
+
+	list_for_each_entry_safe(buf, tmp, &done_list, list)
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+}
+
+static const struct vb2_ops vcam_vb2_ops = {
+	.queue_setup = vcam_vb2_queue_setup,
+	.buf_queue = vcam_vb2_buf_queue,
+	.buf_prepare = vcam_vb2_buf_prepare,
+	.prepare_streaming = vcam_vb2_prepare_streaming,
+	.start_streaming = vcam_vb2_start_streaming,
+	.stop_streaming = vcam_vb2_stop_streaming,
+};
+
+static int vcam_ioc_create(struct file *file, struct vcam *dev,
+			   struct vcam_ioc_create *config, char *card_label,
+			   unsigned int len)
+{
+	struct vcam_mode *modes = NULL;
+	struct v4l2_format try_fmt;
+	struct video_device *vdev;
+	struct vb2_queue *queue;
+	struct v4l2_format fmt;
+	long ret;
+	u32 i;
+
+	strscpy(dev->v4l2_dev.name, "vcam", sizeof(dev->v4l2_dev.name));
+
+	ret = v4l2_device_register(NULL, &dev->v4l2_dev);
+	if (ret)
+		return ret;
+
+	vdev = video_device_alloc();
+	if (!vdev) {
+		ret = -ENOMEM;
+		goto err_unregister;
+	}
+
+	dev->vdev = vdev;
+	video_set_drvdata(vdev, dev);
+	memcpy(vdev->name, card_label, len);
+	vdev->name[len] = '\0';
+	vdev->vfl_type = VFL_TYPE_VIDEO;
+	vdev->fops = &vcam_vdev_fops;
+	vdev->ioctl_ops = &vcam_ioctl_ops;
+	vdev->release = &video_device_release;
+	vdev->minor = -1;
+	vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
+	vdev->vfl_dir = VFL_DIR_RX;
+
+	mutex_init(&dev->lock);
+	spin_lock_init(&dev->frame_lock);
+	spin_lock_init(&dev->status_lock);
+	INIT_LIST_HEAD(&dev->capture_list);
+	INIT_LIST_HEAD(&dev->output_list);
+	dev->status = VCAM_STATUS_IDLE;
+	dev->output_memory = VB2_MEMORY_DMABUF;
+	init_waitqueue_head(&dev->status_waitq);
+
+	dev->vdev->v4l2_dev = &dev->v4l2_dev;
+	dev->vdev->queue = &dev->capture_queue;
+	dev->vdev->lock = &dev->lock;
+	dev->capture.capability = 0;
+	dev->capture.capturemode = 0;
+	dev->capture.extendedmode = 0;
+	dev->capture.readbuffers = VCAM_MIN_FRAMES;
+	dev->capture.timeperframe.numerator = 1;
+	dev->capture.timeperframe.denominator = 30;
+
+	if (!IS_ENABLED(CONFIG_DMA_SHARED_BUFFER) ||
+	    !vb2_vmalloc_memops.attach_dmabuf) {
+		ret = -EOPNOTSUPP;
+		goto err_unregister;
+	}
+
+	modes = kcalloc(config->nr_modes, sizeof(*modes), GFP_KERNEL);
+	if (!modes) {
+		ret = -ENOMEM;
+		goto err_unregister;
+	}
+
+	if (copy_from_user(modes, u64_to_user_ptr(config->modes),
+			   config->nr_modes * sizeof(*modes))) {
+		ret = -EFAULT;
+		goto err_modes;
+	}
+
+	for (i = 0; i < config->nr_modes; i++) {
+		struct vcam_mode *mode = &modes[i];
+
+		if (!mode->width || !mode->height || !mode->pixelformat) {
+			ret = -EINVAL;
+			goto err_modes;
+		}
+
+		if (memchr_inv(mode->reserved, 0, sizeof(mode->reserved))) {
+			ret = -EINVAL;
+			goto err_modes;
+		}
+
+		fmt = (struct v4l2_format){
+			.type = V4L2_BUF_TYPE_VIDEO_OUTPUT,
+			.fmt.pix = { .width = mode->width,
+				     .height = mode->height,
+				     .pixelformat = mode->pixelformat,
+				     .colorspace = mode->colorspace,
+				     .bytesperline = mode->stride,
+				     .field = V4L2_FIELD_NONE }
+		};
+
+		try_fmt = fmt;
+		ret = vcam_set_format(&try_fmt);
+		if (ret)
+			goto err_modes;
+
+		if (try_fmt.fmt.pix.width != mode->width ||
+		    try_fmt.fmt.pix.height != mode->height ||
+		    try_fmt.fmt.pix.pixelformat != mode->pixelformat ||
+		    (mode->colorspace != V4L2_COLORSPACE_DEFAULT &&
+		     try_fmt.fmt.pix.colorspace != mode->colorspace) ||
+		    (mode->stride &&
+		     try_fmt.fmt.pix.bytesperline != mode->stride)) {
+			ret = -EINVAL;
+			goto err_modes;
+		}
+
+		mode->colorspace = try_fmt.fmt.pix.colorspace;
+		mode->stride = try_fmt.fmt.pix.bytesperline;
+	}
+
+	sort(modes, config->nr_modes, sizeof(*modes), vcam_mode_cmp, NULL);
+	for (i = 1; i < config->nr_modes; i++) {
+		if (vcam_mode_equal(&modes[i - 1], &modes[i])) {
+			ret = -EINVAL;
+			goto err_modes;
+		}
+	}
+
+	dev->modes = modes;
+	dev->nr_modes = config->nr_modes;
+	modes = NULL;
+	dev->pix_format = (struct v4l2_pix_format){
+		.width = dev->modes[0].width,
+		.height = dev->modes[0].height,
+		.pixelformat = dev->modes[0].pixelformat,
+		.colorspace = dev->modes[0].colorspace,
+		.bytesperline = dev->modes[0].stride,
+		.field = V4L2_FIELD_NONE,
+	};
+
+	fmt = (struct v4l2_format){
+		.type = V4L2_BUF_TYPE_VIDEO_OUTPUT,
+		.fmt.pix = dev->pix_format,
+	};
+
+	ret = vcam_set_format(&fmt);
+	if (ret)
+		goto err_unregister;
+
+	dev->pix_format = fmt.fmt.pix;
+
+	queue = &dev->capture_queue;
+	queue->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	queue->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
+	queue->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	queue->drv_priv = dev;
+	queue->buf_struct_size = sizeof(struct vcam_buf);
+	queue->ops = &vcam_vb2_ops;
+	queue->mem_ops = &vb2_vmalloc_memops;
+	queue->lock = &dev->lock;
+	queue->dev = &dev->vdev->dev;
+	ret = vb2_queue_init(queue);
+	if (ret)
+		goto err_unregister;
+
+	queue = &dev->output_queue;
+	queue->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+	queue->io_modes = VB2_DMABUF;
+	queue->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+	queue->drv_priv = dev;
+	queue->buf_struct_size = sizeof(struct vcam_buf);
+	queue->ops = &vcam_vb2_ops;
+	queue->mem_ops = &vb2_vmalloc_memops;
+	queue->lock = &dev->lock;
+	queue->dev = &dev->vdev->dev;
+	ret = vb2_queue_init(queue);
+	if (ret)
+		goto err_capture_queue;
+
+	ret = vcam_ioc_alloc(file, dev, config->nr_frames,
+			     u64_to_user_ptr(config->frames),
+			     VB2_MEMORY_DMABUF);
+	if (ret)
+		goto err_output_queue;
+
+	ret = video_register_device(dev->vdev, VFL_TYPE_VIDEO, -1);
+	if (ret < 0)
+		goto err_output_queue;
+
+	config->device_nr = dev->vdev->num;
+	return 0;
+
+err_output_queue:
+	vb2_queue_release(&dev->output_queue);
+
+err_capture_queue:
+	vb2_queue_release(&dev->capture_queue);
+
+err_unregister:
+	if (dev->vdev)
+		video_device_release(dev->vdev);
+	v4l2_device_unregister(&dev->v4l2_dev);
+err_modes:
+	kfree(dev->modes);
+	dev->modes = NULL;
+	dev->nr_modes = 0;
+	kfree(modes);
+	return ret;
+}
+
+static long vcam_ioctl(struct file *file, unsigned int cmd, unsigned long parm)
+{
+	struct vcam *dev = file->private_data;
+	char card_label[VCAM_CARD_LABEL_MAX];
+	struct vcam_ioc_create config;
+	long ret, len;
+
+	if (cmd != VCAM_IOC_CREATE) {
+		if (!dev || !test_bit(VCAM_FLAG_CREATING, &dev->flags) ||
+		    !test_bit(VCAM_FLAG_READY, &dev->flags) ||
+		    dev->device_nr < 0)
+			return -ENOTTY;
+		return vcam_ioctl_common(file, cmd, parm);
+	}
+
+	if (!dev)
+		return -ENOTTY;
+
+	if (test_and_set_bit(VCAM_FLAG_CREATING, &dev->flags))
+		return -EBUSY;
+
+	if (!parm) {
+		ret = -EINVAL;
+		goto err_clear;
+	}
+
+	if (copy_from_user(&config, (void *)parm, sizeof(config))) {
+		ret = -EFAULT;
+		goto err_clear;
+	}
+
+	len = vcam_ioc_create_validate(&config, card_label);
+	if (len < 0) {
+		ret = len;
+		goto err_clear;
+	}
+
+	ret = vcam_ioc_create(file, dev, &config, card_label, len);
+	if (ret)
+		goto err_clear;
+
+	if (copy_to_user((void *)parm, &config, sizeof(config))) {
+		ret = -EFAULT;
+		goto err_release;
+	}
+
+	dev->device_nr = dev->vdev->num;
+	snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name), "vcam-%d",
+		 dev->device_nr);
+	set_bit(VCAM_FLAG_READY, &dev->flags);
+	return 0;
+
+err_release:
+	__vcam_release(dev);
+
+err_clear:
+	clear_bit(VCAM_FLAG_CREATING, &dev->flags);
+	return ret;
+}
+
+static const struct file_operations vcam_fops = {
+	.owner = THIS_MODULE,
+	.open = vcam_open,
+	.unlocked_ioctl = vcam_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl = vcam_ioctl,
+#endif
+	.poll = vcam_poll,
+	.mmap = vcam_mmap,
+	.release = vcam_close,
+	.llseek = noop_llseek,
+};
+
+static struct miscdevice vcam_misc = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name = "vcam",
+	.fops = &vcam_fops,
+	.groups = vcam_attr_groups,
+};
+
+module_misc_device(vcam_misc);
diff --git a/include/uapi/linux/vcam.h b/include/uapi/linux/vcam.h
new file mode 100644
index 000000000000..a4b4d611ac58
--- /dev/null
+++ b/include/uapi/linux/vcam.h
@@ -0,0 +1,141 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ * Copyright (c) Jarkko Sakkinen 2025-2026
+ */
+
+#ifndef _UAPI_LINUX_VCAM_H
+#define _UAPI_LINUX_VCAM_H
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+#define VCAM_IOC_BASE 'v'
+
+/**
+ * DOC: vcam uAPI
+ *
+ * The ioctl API of /dev/vcam provides ioctls for creating DMA-BUF backed
+ * virtual capture devices, and pushing image frames for consumption.
+ *
+ * Frames are queued with %VCAM_IOC_QUEUE and recycled with %VCAM_IOC_DEQUEUE.
+ * Queueing without dequeuing eventually exhausts the output queue.
+ */
+
+/**
+ * enum vcam_status - Status bits
+ * @VCAM_STATUS_IDLE: Capture queue is not streaming.
+ * @VCAM_STATUS_STREAMING: Capture queue is streaming.
+ */
+enum vcam_status {
+	VCAM_STATUS_IDLE = 1U << 0,
+	VCAM_STATUS_STREAMING = 1U << 1,
+};
+
+/**
+ * struct vcam_mode - Supported capture mode
+ * @width: Frame width in pixels.
+ * @height: Frame height in pixels.
+ * @pixelformat: Four CC format code.
+ * @colorspace: V4L2 colorspace value.
+ * @stride: Bytes per line in the output format.
+ * @reserved: Reserved for future use. Must be set to zero.
+ */
+struct vcam_mode {
+	__u32 width;
+	__u32 height;
+	__u32 pixelformat;
+	__u32 colorspace;
+	__u32 stride;
+	__u8 reserved[12];
+};
+
+/**
+ * struct vcam_ioc_create - Create a virtual camera device
+ * @device_name: (input) User pointer to device name string.
+ * @device_nr: (output) Device number (must be 0 on input).
+ * @nr_modes: (input) Number of entries in @modes.
+ * @modes: (input) User pointer to an array of &struct vcam_mode.
+ * @reserved: Reserved for future use. Must be set to zero.
+ * @nr_frames: (input) Number of entries in @frames.
+ * @frames: (input/output) User pointer to an array of &struct vcam_frame.
+ */
+struct vcam_ioc_create {
+	__u64 device_name;
+	__u32 device_nr;
+	__u32 nr_modes;
+	__u64 modes;
+	__u32 reserved;
+	__u32 nr_frames;
+	__u64 frames;
+};
+
+/**
+ * struct vcam_frame - a frame descriptor
+ * @index: Frame index assigned by the driver.
+ * @length: Frame size in bytes.
+ */
+struct vcam_frame {
+	__u32 index;
+	__u32 length;
+};
+
+/**
+ * struct vcam_ioc_queue - Produce an output buffer
+ * @fd: (input) DMA-BUF file descriptor.
+ * @index: (input) Buffer index for %VCAM_IOC_QUEUE.
+ * @length: (input) Payload length in bytes for %VCAM_IOC_QUEUE.
+ * @reserved: Reserved for future use. Must be set to zero.
+ * @timestamp: (input) Timestamp in nanoseconds for %VCAM_IOC_QUEUE.
+ */
+struct vcam_ioc_queue {
+	__u32 fd;
+	__u32 index;
+	__u32 length;
+	__u32 reserved;
+	__u64 timestamp;
+};
+
+/**
+ * struct vcam_ioc_dequeue - Dequeue an output buffer
+ * @index: (output) Buffer index for %VCAM_IOC_DEQUEUE.
+ * @length: (output) Payload length in bytes for %VCAM_IOC_DEQUEUE.
+ * @timestamp: (output) Timestamp in nanoseconds for %VCAM_IOC_DEQUEUE.
+ */
+struct vcam_ioc_dequeue {
+	__u32 index;
+	__u32 length;
+	__u64 timestamp;
+};
+
+/**
+ * struct vcam_ioc_wait - Wait for capture status
+ * @mask: (input) Mask of status bits to wait for. Set to zero to return
+ *         immediately.
+ * @status: (output) Current status bit mask.
+ * @mode: (output) User pointer to &struct vcam_mode.
+ * @reserved: Reserved for future use. Must be set to zero.
+ */
+struct vcam_ioc_wait {
+	__u64 mask;
+	__u64 status;
+	__u64 mode;
+	__u64 reserved;
+};
+
+/**
+ * DOC: vcam ioctls
+ *
+ * %VCAM_IOC_CREATE: Creates a virtual camera device, stores the allowed capture
+ * modes, and associates output buffers described by &struct vcam_frame with
+ * DMA-BUF file descriptors.
+ * %VCAM_IOC_QUEUE: Enqueues an output buffer for capture.
+ * %VCAM_IOC_DEQUEUE: Dequeues a consumed output buffer for reuse.
+ * %VCAM_IOC_WAIT: Waits for the subset of status bits to activate and returns
+ * the current status and capture mode.
+ */
+#define VCAM_IOC_CREATE _IOWR(VCAM_IOC_BASE, 0x00, struct vcam_ioc_create)
+#define VCAM_IOC_QUEUE _IOW(VCAM_IOC_BASE, 0x01, struct vcam_ioc_queue)
+#define VCAM_IOC_DEQUEUE _IOR(VCAM_IOC_BASE, 0x02, struct vcam_ioc_dequeue)
+#define VCAM_IOC_WAIT _IOWR(VCAM_IOC_BASE, 0x04, struct vcam_ioc_wait)
+
+#endif /* _UAPI_LINUX_VCAM_H */
-- 
2.52.0


^ permalink raw reply related

* Re: [PATCH v4 00/17] module: Introduce hash-based integrity checking
From: Eric Biggers @ 2026-02-02 18:47 UTC (permalink / raw)
  To: David Howells
  Cc: =?UTF-8?q?Mihai-Drosi=20C=C3=A2ju?=, linux, arnd, arnout, atomlin,
	bigeasy, chleroy, christian, corbet, coxu, da.gomez, da.gomez,
	dmitry.kasatkin, eric.snowberg, f.gruenbichler, jmorris, kpcyrd,
	linux-arch, linux-doc, linux-integrity, linux-kbuild,
	linux-kernel, linux-modules, linux-security-module, linuxppc-dev,
	lkp, maddy, mattia, mcgrof, mpe, nathan, naveen,
	nicolas.bouchinet, nicolas.schier, npiggin, nsc, paul, petr.pavlu,
	roberto.sassu, samitolvanen, serge, xiujianfeng, zohar
In-Reply-To: <2513499.1770057531@warthog.procyon.org.uk>

On Mon, Feb 02, 2026 at 06:38:51PM +0000, David Howells wrote:
> > Could you give more details on this use case and why it needs
> > signatures, as opposed to e.g. loading an additional Merkle tree root
> > into the kernel to add to the set of allowed modules?
> 
> Because we don't want to, for example, include all the nvidia drivers in our
> kernel SRPM.

That doesn't answer my question.  Are you trying to say these modules
need to be built later *and* signed using the original signing key?

- Eric

^ permalink raw reply

* Re: [PATCH v4 00/17] module: Introduce hash-based integrity checking
From: David Howells @ 2026-02-02 18:38 UTC (permalink / raw)
  To: Eric Biggers
  Cc: dhowells, =?UTF-8?q?Mihai-Drosi=20C=C3=A2ju?=, linux, arnd,
	arnout, atomlin, bigeasy, chleroy, christian, corbet, coxu,
	da.gomez, da.gomez, dmitry.kasatkin, eric.snowberg,
	f.gruenbichler, jmorris, kpcyrd, linux-arch, linux-doc,
	linux-integrity, linux-kbuild, linux-kernel, linux-modules,
	linux-security-module, linuxppc-dev, lkp, maddy, mattia, mcgrof,
	mpe, nathan, naveen, nicolas.bouchinet, nicolas.schier, npiggin,
	nsc, paul, petr.pavlu, roberto.sassu, samitolvanen, serge,
	xiujianfeng, zohar
In-Reply-To: <20260202183055.GB2036@quark>

Eric Biggers <ebiggers@kernel.org> wrote:

> On Mon, Feb 02, 2026 at 09:21:19AM +0000, David Howells wrote:
> > Eric Biggers <ebiggers@kernel.org> wrote:
> > 
> > > With that being the case, why is there still effort being put into
> > > adding more features to module signing?  I would think efforts should be
> > > focused on hash-based module authentication, i.e. this patchset.
> > 
> > Because it's not just signing of modules
> 
> Module signing is indeed about the signing of modules.

The signature verification stuff in the kernel isn't just used for modules.
kexec, for instance; wifi restriction database for another.

> > and it's not just modules built with the kernel.
> 
> Could you give more details on this use case and why it needs
> signatures, as opposed to e.g. loading an additional Merkle tree root
> into the kernel to add to the set of allowed modules?

Because we don't want to, for example, include all the nvidia drivers in our
kernel SRPM.

David


^ permalink raw reply

* Re: [PATCH v4 00/17] module: Introduce hash-based integrity checking
From: Eric Biggers @ 2026-02-02 18:30 UTC (permalink / raw)
  To: David Howells
  Cc: =?UTF-8?q?Mihai-Drosi=20C=C3=A2ju?=, linux, arnd, arnout, atomlin,
	bigeasy, chleroy, christian, corbet, coxu, da.gomez, da.gomez,
	dmitry.kasatkin, eric.snowberg, f.gruenbichler, jmorris, kpcyrd,
	linux-arch, linux-doc, linux-integrity, linux-kbuild,
	linux-kernel, linux-modules, linux-security-module, linuxppc-dev,
	lkp, maddy, mattia, mcgrof, mpe, nathan, naveen,
	nicolas.bouchinet, nicolas.schier, npiggin, nsc, paul, petr.pavlu,
	roberto.sassu, samitolvanen, serge, xiujianfeng, zohar
In-Reply-To: <2339369.1770024079@warthog.procyon.org.uk>

On Mon, Feb 02, 2026 at 09:21:19AM +0000, David Howells wrote:
> Eric Biggers <ebiggers@kernel.org> wrote:
> 
> > With that being the case, why is there still effort being put into
> > adding more features to module signing?  I would think efforts should be
> > focused on hash-based module authentication, i.e. this patchset.
> 
> Because it's not just signing of modules

Module signing is indeed about the signing of modules.

> and it's not just modules built with the kernel.

Could you give more details on this use case and why it needs
signatures, as opposed to e.g. loading an additional Merkle tree root
into the kernel to add to the set of allowed modules?

> Also a hash table just of module hashes built into the core
> kernel image will increase the size of the kernel by around a third of a meg
> (on Fedora 43 and assuming SHA512) with uncompressible data.

This patchset already optimizes it to use Merkle tree proofs instead.
While I'm a bit skeptical of the complexity myself (and distros
shouldn't be shipping such an excessively large number of modules in the
first place), if it's indeed needed it's already been solved.  It's
still much simpler than the PKCS#7 signature mess.

- Eric

^ permalink raw reply

* Re: [PATCH v4 00/17] module: Introduce hash-based integrity checking
From: David Howells @ 2026-02-02  9:21 UTC (permalink / raw)
  To: Eric Biggers
  Cc: dhowells, =?UTF-8?q?Mihai-Drosi=20C=C3=A2ju?=, linux, arnd,
	arnout, atomlin, bigeasy, chleroy, christian, corbet, coxu,
	da.gomez, da.gomez, dmitry.kasatkin, eric.snowberg,
	f.gruenbichler, jmorris, kpcyrd, linux-arch, linux-doc,
	linux-integrity, linux-kbuild, linux-kernel, linux-modules,
	linux-security-module, linuxppc-dev, lkp, maddy, mattia, mcgrof,
	mpe, nathan, naveen, nicolas.bouchinet, nicolas.schier, npiggin,
	nsc, paul, petr.pavlu, roberto.sassu, samitolvanen, serge,
	xiujianfeng, zohar
In-Reply-To: <20260201201218.GA15755@quark>

Eric Biggers <ebiggers@kernel.org> wrote:

> With that being the case, why is there still effort being put into
> adding more features to module signing?  I would think efforts should be
> focused on hash-based module authentication, i.e. this patchset.

Because it's not just signing of modules and it's not just modules built with
the kernel.  Also a hash table just of module hashes built into the core
kernel image will increase the size of the kernel by around a third of a meg
(on Fedora 43 and assuming SHA512) with uncompressible data.

David


^ permalink raw reply

* Re: [PATCH v15 01/28] tpm: Initial step to reorganize TPM public headers
From: Jarkko Sakkinen @ 2026-02-01 22:46 UTC (permalink / raw)
  To: Daniel P. Smith
  Cc: Ross Philipson, linux-kernel, x86, linux-integrity, linux-doc,
	linux-crypto, kexec, linux-efi, iommu, tglx, mingo, bp, hpa,
	dave.hansen, ardb, mjg59, James.Bottomley, peterhuewe, jgg, luto,
	nivedita, herbert, davem, corbet, ebiederm, dwmw2, baolu.lu,
	kanth.ghatraju, andrew.cooper3, trenchboot-devel
In-Reply-To: <b94815dc-fc4d-4073-bfd6-31ab99a6b85b@apertussolutions.com>

On Sun, Feb 01, 2026 at 11:20:20AM -0500, Daniel P. Smith wrote:
> On 1/19/26 18:40, Jarkko Sakkinen wrote:
> > On Mon, Dec 15, 2025 at 03:32:49PM -0800, Ross Philipson wrote:
> > > Replace the existing public header tpm_command.h with the first two
> > > new public headers tpm1.h and tpm_common.h. In addition, related
> > > definitions in tpm1_cmd.c were moved to the new tpm1.h.
> > > 
> > > Signed-off-by: Daniel P. Smith <dpsmith@apertussolutions.com>
> > > Signed-off-by: Ross Philipson <ross.philipson@oracle.com>
> > > ---
> > >   drivers/char/tpm/tpm-buf.c                |  3 +-
> > >   drivers/char/tpm/tpm1-cmd.c               | 13 +-----
> > >   include/keys/trusted_tpm.h                |  1 -
> > >   include/linux/tpm.h                       |  3 ++
> > >   include/linux/tpm1.h                      | 55 +++++++++++++++++++++++
> > >   include/linux/tpm_command.h               | 30 -------------
> > 
> > Removing tpm_command.h causes unnecessary noise.
> > 
> > It would be better to retain tpm_command.h, and simply supplement
> > it with TPM2 constants.
> > 
> > Also, what is the reason to not have both TPM1 and TPM2 in tpm.h?
> > 
> > To put the question in other words: is there something in tpm.h that
> > would be incompatible with early boot code?
> > 
> > I'd rather tweak that than have more files...
> 
> Every #include in tpm.h will break in the early boot code. I don't see any
> way to avoid having one header that is the device driver header that
> integrates with mainline features and at least one header that holds the
> general TPM definitions.
> 
> We will move everything that was broken out into tpm_command.h, making it
> the header with the general definitions. I would raise the question of
> whether tpm_command.h would be the best name of the file after definition
> reloactions

tpm_command.h conforms with lowest common denominator in the sense that
it in its current state it is compatible with early boot code.

I'd consolidate TPM1 and TPM2 definitions that can be shared to that
header. I guess rename is fine as long as protocol definitions are
not scattered to two headers.

BR, Jarkko

^ permalink raw reply

* Re: [PATCH v6 6/6] docs: trusted-encryped: add PKWM as a new trust source
From: Jarkko Sakkinen @ 2026-02-01 22:29 UTC (permalink / raw)
  To: Srish Srinivasan
  Cc: linux-integrity, keyrings, linuxppc-dev, maddy, mpe, npiggin,
	christophe.leroy, James.Bottomley, zohar, nayna, rnsastry,
	linux-kernel, linux-security-module
In-Reply-To: <20260201135930.898721-7-ssrish@linux.ibm.com>

On Sun, Feb 01, 2026 at 07:29:30PM +0530, Srish Srinivasan wrote:
> From: Nayna Jain <nayna@linux.ibm.com>
> 
> Update Documentation/security/keys/trusted-encrypted.rst and Documentation/
> admin-guide/kernel-parameters.txt with PowerVM Key Wrapping Module (PKWM)
> as a new trust source
> 
> Signed-off-by: Nayna Jain <nayna@linux.ibm.com>
> Signed-off-by: Srish Srinivasan <ssrish@linux.ibm.com>
> Reviewed-by: Mimi Zohar <zohar@linux.ibm.com>

Reviewed-by: Jarkko Sakkinen <jarkko@kernel.org>

And you are free to take 5/6 and 6/6 to a pull request if you prefer
that route.

> ---
>  .../admin-guide/kernel-parameters.txt         |  1 +
>  .../security/keys/trusted-encrypted.rst       | 50 +++++++++++++++++++
>  2 files changed, 51 insertions(+)
> 
> diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
> index 1058f2a6d6a8..aac15079b33d 100644
> --- a/Documentation/admin-guide/kernel-parameters.txt
> +++ b/Documentation/admin-guide/kernel-parameters.txt
> @@ -7790,6 +7790,7 @@ Kernel parameters
>  			- "tee"
>  			- "caam"
>  			- "dcp"
> +			- "pkwm"
>  			If not specified then it defaults to iterating through
>  			the trust source list starting with TPM and assigns the
>  			first trust source as a backend which is initialized
> diff --git a/Documentation/security/keys/trusted-encrypted.rst b/Documentation/security/keys/trusted-encrypted.rst
> index eae6a36b1c9a..ddff7c7c2582 100644
> --- a/Documentation/security/keys/trusted-encrypted.rst
> +++ b/Documentation/security/keys/trusted-encrypted.rst
> @@ -81,6 +81,14 @@ safe.
>           and the UNIQUE key. Default is to use the UNIQUE key, but selecting
>           the OTP key can be done via a module parameter (dcp_use_otp_key).
>  
> +     (5) PKWM (PowerVM Key Wrapping Module: IBM PowerVM + Platform KeyStore)
> +
> +         Rooted to a unique, per-LPAR key, which is derived from a system-wide,
> +         randomly generated LPAR root key. Both the per-LPAR keys and the LPAR
> +         root key are stored in hypervisor-owned secure memory at runtime,
> +         and the LPAR root key is additionally persisted in secure locations
> +         such as the processor SEEPROMs and encrypted NVRAM.
> +
>    *  Execution isolation
>  
>       (1) TPM
> @@ -102,6 +110,14 @@ safe.
>           environment. Only basic blob key encryption is executed there.
>           The actual key sealing/unsealing is done on main processor/kernel space.
>  
> +     (5) PKWM (PowerVM Key Wrapping Module: IBM PowerVM + Platform KeyStore)
> +
> +         Fixed set of cryptographic operations done on on-chip hardware
> +         cryptographic acceleration unit NX. Keys for wrapping and unwrapping
> +         are managed by PowerVM Platform KeyStore, which stores keys in an
> +         isolated in-memory copy in secure hypervisor memory, as well as in a
> +         persistent copy in hypervisor-encrypted NVRAM.
> +
>    * Optional binding to platform integrity state
>  
>       (1) TPM
> @@ -129,6 +145,11 @@ safe.
>           Relies on Secure/Trusted boot process (called HAB by vendor) for
>           platform integrity.
>  
> +     (5) PKWM (PowerVM Key Wrapping Module: IBM PowerVM + Platform KeyStore)
> +
> +         Relies on secure and trusted boot process of IBM Power systems for
> +         platform integrity.
> +
>    *  Interfaces and APIs
>  
>       (1) TPM
> @@ -149,6 +170,11 @@ safe.
>           Vendor-specific API that is implemented as part of the DCP crypto driver in
>           ``drivers/crypto/mxs-dcp.c``.
>  
> +     (5) PKWM (PowerVM Key Wrapping Module: IBM PowerVM + Platform KeyStore)
> +
> +         Platform Keystore has well documented interfaces in PAPR document.
> +         Refer to ``Documentation/arch/powerpc/papr_hcalls.rst``
> +
>    *  Threat model
>  
>       The strength and appropriateness of a particular trust source for a given
> @@ -191,6 +217,10 @@ selected trust source:
>       a dedicated hardware RNG that is independent from DCP which can be enabled
>       to back the kernel RNG.
>  
> +   * PKWM (PowerVM Key Wrapping Module: IBM PowerVM + Platform KeyStore)
> +
> +     The normal kernel random number generator is used to generate keys.
> +
>  Users may override this by specifying ``trusted.rng=kernel`` on the kernel
>  command-line to override the used RNG with the kernel's random number pool.
>  
> @@ -321,6 +351,26 @@ Usage::
>  specific to this DCP key-blob implementation.  The key length for new keys is
>  always in bytes. Trusted Keys can be 32 - 128 bytes (256 - 1024 bits).
>  
> +Trusted Keys usage: PKWM
> +------------------------
> +
> +Usage::
> +
> +    keyctl add trusted name "new keylen [options]" ring
> +    keyctl add trusted name "load hex_blob" ring
> +    keyctl print keyid
> +
> +    options:
> +       wrap_flags=   ascii hex value of security policy requirement
> +                       0x00: no secure boot requirement (default)
> +                       0x01: require secure boot to be in either audit or
> +                             enforced mode
> +                       0x02: require secure boot to be in enforced mode
> +
> +"keyctl print" returns an ASCII hex copy of the sealed key, which is in format
> +specific to PKWM key-blob implementation.  The key length for new keys is
> +always in bytes. Trusted Keys can be 32 - 128 bytes (256 - 1024 bits).
> +
>  Encrypted Keys usage
>  --------------------
>  
> -- 
> 2.47.3
> 

BR, Jarkko

^ permalink raw reply

* Re: [PATCH v9 01/11] KEYS: trusted: Use get_random-fallback for TPM
From: Jarkko Sakkinen @ 2026-02-01 22:25 UTC (permalink / raw)
  To: Roberto Sassu
  Cc: linux-integrity, Eric Biggers, James Bottomley, Mimi Zohar,
	David Howells, Paul Moore, James Morris, Serge E. Hallyn,
	open list:KEYS-TRUSTED, open list:SECURITY SUBSYSTEM, open list
In-Reply-To: <facea3621fc240ebb05dedb0127d8a514970d40d.camel@huaweicloud.com>

On Thu, Jan 29, 2026 at 05:18:55PM +0100, Roberto Sassu wrote:
> On Sun, 2026-01-25 at 21:25 +0200, Jarkko Sakkinen wrote:
> > 1. tpm2_get_random() is costly when TCG_TPM2_HMAC is enabled and thus its
> >    use should be pooled rather than directly used. This both reduces
> >    latency and improves its predictability.
> > 
> > 2. Linux is better off overall if every subsystem uses the same source for
> >    generating the random numbers required.
> > 
> > Thus, unset '.get_random', which causes fallback to kernel_get_random().
> > 
> > One might argue that TPM RNG should be used for the generated trusted keys,
> > so that they have matching entropy with the TPM internally generated
> > objects.
> > 
> > This argument does have some weight into it but as far cryptography goes,
> > FIPS certification sets the exact bar, not which exact FIPS certified RNG
> > will be used. Thus, the rational choice is obviously to pick the lowest
> > latency path, which is kernel RNG.
> > 
> > Finally, there is an actual defence in depth benefit when using kernel RNG
> > as it helps to mitigate TPM firmware bugs concerning RNG implementation,
> > given the obfuscation by the other entropy sources.
> > 
> > Reviewed-by: Eric Biggers <ebiggers@kernel.org>
> > Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
> > ---
> > v7:
> > - A new patch. Simplifies follow up patches.
> > ---
> >  security/keys/trusted-keys/trusted_tpm1.c | 16 ++++++++++------
> >  1 file changed, 10 insertions(+), 6 deletions(-)
> > 
> > diff --git a/security/keys/trusted-keys/trusted_tpm1.c b/security/keys/trusted-keys/trusted_tpm1.c
> > index 636acb66a4f6..7ce7e31bcdfb 100644
> > --- a/security/keys/trusted-keys/trusted_tpm1.c
> > +++ b/security/keys/trusted-keys/trusted_tpm1.c
> > @@ -6,6 +6,16 @@
> >   * See Documentation/security/keys/trusted-encrypted.rst
> >   */
> >  
> > +/**
> > + * DOC: Random Number Generation
> > + *
> > + * tpm_get_random() was previously used here as the RNG in order to have equal
> > + * entropy with the objects fully inside the TPM. However, as far as goes,
> > + * kernel RNG is equally fine, as long as long as it is FIPS certified. Also,
> > + * using kernel RNG has the benefit of mitigating bugs in the TPM firmware
> > + * associated with the RNG.
> > + */
> 
> If we switch to the kernel RNG that is better, and the TPM one is
> flawed, I guess we are going to have big problems anyway, since the TPM
> random number generator is used by the TPM itself internally.
> 
> I think it makes sense to leave as it is.

There's neither really formal case for not doing this unless the random
number provided by TPM would be opaque to kernel because as soon as CPU
touches it, the "risk" matches kernel RNG generated random number.

These change do have a measurable benefit as they  objectively decrease
TPM traffic.

And as we probably know, security certifications do not really apply
simply by using TPM RNG.

BR, Jarkko

^ permalink raw reply

* Re: [PATCH v4 00/17] module: Introduce hash-based integrity checking
From: Eric Biggers @ 2026-02-01 20:12 UTC (permalink / raw)
  To: David Howells
  Cc: =?UTF-8?q?Mihai-Drosi=20C=C3=A2ju?=, linux, arnd, arnout, atomlin,
	bigeasy, chleroy, christian, corbet, coxu, da.gomez, da.gomez,
	dmitry.kasatkin, eric.snowberg, f.gruenbichler, jmorris, kpcyrd,
	linux-arch, linux-doc, linux-integrity, linux-kbuild,
	linux-kernel, linux-modules, linux-security-module, linuxppc-dev,
	lkp, maddy, mattia, mcgrof, mpe, nathan, naveen,
	nicolas.bouchinet, nicolas.schier, npiggin, nsc, paul, petr.pavlu,
	roberto.sassu, samitolvanen, serge, xiujianfeng, zohar
In-Reply-To: <2316630.1769965788@warthog.procyon.org.uk>

On Sun, Feb 01, 2026 at 05:09:48PM +0000, David Howells wrote:
> Mihai-Drosi Câju <mcaju95@gmail.com> wrote:
> 
> > > The current signature-based module integrity checking has some drawbacks
> > in combination with reproducible builds. Either the module signing key
> > is generated at build time, which makes the build unreproducible, or a
> > static signing key is used, which precludes rebuilds by third parties
> > and makes the whole build and packaging process much more complicated.
> 
> There is another issue too: If you have a static private key that you use to
> sign modules (and probably other things), someone will likely give you a GPL
> request to get it.
> 
> One advantage of using a transient key every build and deleting it after is
> that no one has the key.
> 
> One other thing to remember: security is *meant* to get in the way.  That's
> the whole point of it.
> 
> However, IANAL.
> 
> David

It sounds like hash-based module authentication is just better, then.
If the full set of authentic modules is known at kernel build time, then
signatures are unnecessary to verify their authenticity: a list of
hashes built into the kernel image is perfectly sufficient.

(This patchset actually gets a little fancy and makes it a Merkle tree
root.  But it could be simplified to just a list of hashes.)

With that being the case, why is there still effort being put into
adding more features to module signing?  I would think efforts should be
focused on hash-based module authentication, i.e. this patchset.

- Eric

^ permalink raw reply

* Re: [PATCH v4 00/17] module: Introduce hash-based integrity checking
From: David Howells @ 2026-02-01 17:09 UTC (permalink / raw)
  To: =?UTF-8?q?Mihai-Drosi=20C=C3=A2ju?=
  Cc: dhowells, linux, arnd, arnout, atomlin, bigeasy, chleroy,
	christian, corbet, coxu, da.gomez, da.gomez, dmitry.kasatkin,
	eric.snowberg, f.gruenbichler, jmorris, kpcyrd, linux-arch,
	linux-doc, linux-integrity, linux-kbuild, linux-kernel,
	linux-modules, linux-security-module, linuxppc-dev, lkp, maddy,
	mattia, mcgrof, mpe, nathan, naveen, nicolas.bouchinet,
	nicolas.schier, npiggin, nsc, paul, petr.pavlu, roberto.sassu,
	samitolvanen, serge, xiujianfeng, zohar
In-Reply-To: <20260131073636.65494-1-mcaju95@gmail.com>

Mihai-Drosi Câju <mcaju95@gmail.com> wrote:

> > The current signature-based module integrity checking has some drawbacks
> in combination with reproducible builds. Either the module signing key
> is generated at build time, which makes the build unreproducible, or a
> static signing key is used, which precludes rebuilds by third parties
> and makes the whole build and packaging process much more complicated.

There is another issue too: If you have a static private key that you use to
sign modules (and probably other things), someone will likely give you a GPL
request to get it.

One advantage of using a transient key every build and deleting it after is
that no one has the key.

One other thing to remember: security is *meant* to get in the way.  That's
the whole point of it.

However, IANAL.

David


^ permalink raw reply

* Re: [PATCH v15 02/28] tpm: Move TPM1 specific definitions and functions to new headers
From: Daniel P. Smith @ 2026-02-01 16:23 UTC (permalink / raw)
  To: Jarkko Sakkinen, Ross Philipson
  Cc: linux-kernel, x86, linux-integrity, linux-doc, linux-crypto,
	kexec, linux-efi, iommu, tglx, mingo, bp, hpa, dave.hansen, ardb,
	mjg59, James.Bottomley, peterhuewe, jgg, luto, nivedita, herbert,
	davem, corbet, ebiederm, dwmw2, baolu.lu, kanth.ghatraju,
	andrew.cooper3, trenchboot-devel
In-Reply-To: <aW7E2dVlmjZIUivW@kernel.org>

On 1/19/26 18:57, Jarkko Sakkinen wrote:
> On Mon, Dec 15, 2025 at 03:32:50PM -0800, Ross Philipson wrote:
>> This gathers all the TPM1 definitions and structures into two separate
>> header files (public tpm1.h and private tpm1_structs.h). The definitions
>> moved to these files correspond to the TCG specification for TPM 1 family:
>>
>> TPM 1.2 Main Specification
>>   -  https://trustedcomputinggroup.org/resource/tpm-main-specification/
>>
>> Note that the structures were pulled into tpm1_structs.h to allow their
>> external reuse.
>>
>> Signed-off-by: Daniel P. Smith <dpsmith@apertussolutions.com>
>> Signed-off-by: Ross Philipson <ross.philipson@oracle.com>
>> ---
>>   drivers/char/tpm/tpm.h          | 98 +--------------------------------
>>   drivers/char/tpm/tpm1-cmd.c     |  5 --
>>   drivers/char/tpm/tpm1_structs.h | 97 ++++++++++++++++++++++++++++++++
> 
> I think you are overcomplicating the patch set and doing more
> than you really need to do.
> 
> I.e. structs could go also just as well to tpm_command.h. We
> will deal with if/when that file ever grows too large. It's
> absolutely not a priority for this patch set.

Ack.

>>   include/linux/tpm1.h            | 34 +++++++++++-
>>   4 files changed, 132 insertions(+), 102 deletions(-)
>>   create mode 100644 drivers/char/tpm/tpm1_structs.h
>>
>> diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
>> index ca391b2a211c..1f9f8540eede 100644
>> --- a/drivers/char/tpm/tpm.h
>> +++ b/drivers/char/tpm/tpm.h
>> @@ -50,105 +50,9 @@ enum tpm_addr {
>>   	TPM_ADDR = 0x4E,
>>   };
>>   
>> -#define TPM_WARN_RETRY          0x800
>> -#define TPM_WARN_DOING_SELFTEST 0x802
>> -#define TPM_ERR_DEACTIVATED     0x6
>> -#define TPM_ERR_DISABLED        0x7
>> -#define TPM_ERR_FAILEDSELFTEST  0x1C
>> -#define TPM_ERR_INVALID_POSTINIT 38
>> -
>> -#define TPM_TAG_RQU_COMMAND 193
>> -
>>   /* TPM2 specific constants. */
>>   #define TPM2_SPACE_BUFFER_SIZE		16384 /* 16 kB */
>>   
>> -struct	stclear_flags_t {
>> -	__be16	tag;
>> -	u8	deactivated;
>> -	u8	disableForceClear;
>> -	u8	physicalPresence;
>> -	u8	physicalPresenceLock;
>> -	u8	bGlobalLock;
>> -} __packed;
>> -
>> -struct tpm1_version {
>> -	u8 major;
>> -	u8 minor;
>> -	u8 rev_major;
>> -	u8 rev_minor;
>> -} __packed;
>> -
>> -struct tpm1_version2 {
>> -	__be16 tag;
>> -	struct tpm1_version version;
>> -} __packed;
>> -
>> -struct	timeout_t {
>> -	__be32	a;
>> -	__be32	b;
>> -	__be32	c;
>> -	__be32	d;
>> -} __packed;
>> -
>> -struct duration_t {
>> -	__be32	tpm_short;
>> -	__be32	tpm_medium;
>> -	__be32	tpm_long;
>> -} __packed;
>> -
>> -struct permanent_flags_t {
>> -	__be16	tag;
>> -	u8	disable;
>> -	u8	ownership;
>> -	u8	deactivated;
>> -	u8	readPubek;
>> -	u8	disableOwnerClear;
>> -	u8	allowMaintenance;
>> -	u8	physicalPresenceLifetimeLock;
>> -	u8	physicalPresenceHWEnable;
>> -	u8	physicalPresenceCMDEnable;
>> -	u8	CEKPUsed;
>> -	u8	TPMpost;
>> -	u8	TPMpostLock;
>> -	u8	FIPS;
>> -	u8	operator;
>> -	u8	enableRevokeEK;
>> -	u8	nvLocked;
>> -	u8	readSRKPub;
>> -	u8	tpmEstablished;
>> -	u8	maintenanceDone;
>> -	u8	disableFullDALogicInfo;
>> -} __packed;
>> -
>> -typedef union {
>> -	struct	permanent_flags_t perm_flags;
>> -	struct	stclear_flags_t	stclear_flags;
>> -	__u8	owned;
>> -	__be32	num_pcrs;
>> -	struct tpm1_version version1;
>> -	struct tpm1_version2 version2;
>> -	__be32	manufacturer_id;
>> -	struct timeout_t  timeout;
>> -	struct duration_t duration;
>> -} cap_t;
>> -
>> -enum tpm_capabilities {
>> -	TPM_CAP_FLAG = 4,
>> -	TPM_CAP_PROP = 5,
>> -	TPM_CAP_VERSION_1_1 = 0x06,
>> -	TPM_CAP_VERSION_1_2 = 0x1A,
>> -};
>> -
>> -enum tpm_sub_capabilities {
>> -	TPM_CAP_PROP_PCR = 0x101,
>> -	TPM_CAP_PROP_MANUFACTURER = 0x103,
>> -	TPM_CAP_FLAG_PERM = 0x108,
>> -	TPM_CAP_FLAG_VOL = 0x109,
>> -	TPM_CAP_PROP_OWNER = 0x111,
>> -	TPM_CAP_PROP_TIS_TIMEOUT = 0x115,
>> -	TPM_CAP_PROP_TIS_DURATION = 0x120,
>> -};
>> -
>>   enum tpm2_pt_props {
>>   	TPM2_PT_NONE = 0x00000000,
>>   	TPM2_PT_GROUP = 0x00000100,
>> @@ -229,6 +133,8 @@ enum tpm2_pt_props {
>>    * compiler warnings about stack frame size. */
>>   #define TPM_MAX_RNG_DATA	128
>>   
>> +#include "tpm1_structs.h"
>> +
>>   extern const struct class tpm_class;
>>   extern const struct class tpmrm_class;
>>   extern dev_t tpm_devt;
>> diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c
>> index f29827b454d2..02f20a0aa37d 100644
>> --- a/drivers/char/tpm/tpm1-cmd.c
>> +++ b/drivers/char/tpm/tpm1-cmd.c
>> @@ -505,11 +505,6 @@ ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap,
>>   }
>>   EXPORT_SYMBOL_GPL(tpm1_getcap);
>>   
>> -struct tpm1_get_random_out {
>> -	__be32 rng_data_len;
>> -	u8 rng_data[TPM_MAX_RNG_DATA];
>> -} __packed;
>> -
>>   /**
>>    * tpm1_get_random() - get random bytes from the TPM's RNG
>>    * @chip:	a &struct tpm_chip instance
>> diff --git a/drivers/char/tpm/tpm1_structs.h b/drivers/char/tpm/tpm1_structs.h
>> new file mode 100644
>> index 000000000000..ad21376af5ab
>> --- /dev/null
>> +++ b/drivers/char/tpm/tpm1_structs.h
>> @@ -0,0 +1,97 @@
>> +/* SPDX-License-Identifier: GPL-2.0-only */
>> +/*
>> + * Copyright (C) 2004 IBM Corporation
>> + * Copyright (C) 2015 Intel Corporation
>> + *
>> + * Authors:
>> + * Leendert van Doorn <leendert@watson.ibm.com>
>> + * Dave Safford <safford@watson.ibm.com>
>> + * Reiner Sailer <sailer@watson.ibm.com>
>> + * Kylene Hall <kjhall@us.ibm.com>
>> + *
>> + * Maintained by: <tpmdd-devel@lists.sourceforge.net>
>> + *
>> + * Device driver for TCG/TCPA TPM (trusted platform module).
>> + * Specifications at www.trustedcomputinggroup.org
>> + */
>> +
>> +#ifndef __TPM1_STRUCTS_H__
>> +#define __TPM1_STRUCTS_H__
>> +
>> +struct	stclear_flags_t {
>> +	__be16	tag;
>> +	u8	deactivated;
>> +	u8	disableForceClear;
>> +	u8	physicalPresence;
>> +	u8	physicalPresenceLock;
>> +	u8	bGlobalLock;
>> +} __packed;
> 
> 
> Don't retain alignment.
> 

Okay.

>> +
>> +struct tpm1_version {
>> +	u8 major;
>> +	u8 minor;
>> +	u8 rev_major;
>> +	u8 rev_minor;
>> +} __packed;
>> +
>> +struct tpm1_version2 {
>> +	__be16 tag;
>> +	struct tpm1_version version;
>> +} __packed;
>> +
>> +struct	timeout_t {
>> +	__be32	a;
>> +	__be32	b;
>> +	__be32	c;
>> +	__be32	d;
>> +} __packed;
>> +
>> +struct duration_t {
>> +	__be32	tpm_short;
>> +	__be32	tpm_medium;
>> +	__be32	tpm_long;
>> +} __packed;
>> +
>> +struct permanent_flags_t {
>> +	__be16	tag;
>> +	u8	disable;
>> +	u8	ownership;
>> +	u8	deactivated;
>> +	u8	readPubek;
>> +	u8	disableOwnerClear;
>> +	u8	allowMaintenance;
>> +	u8	physicalPresenceLifetimeLock;
>> +	u8	physicalPresenceHWEnable;
>> +	u8	physicalPresenceCMDEnable;
>> +	u8	CEKPUsed;
>> +	u8	TPMpost;
>> +	u8	TPMpostLock;
>> +	u8	FIPS;
>> +	u8	operator;
>> +	u8	enableRevokeEK;
>> +	u8	nvLocked;
>> +	u8	readSRKPub;
>> +	u8	tpmEstablished;
>> +	u8	maintenanceDone;
>> +	u8	disableFullDALogicInfo;
>> +} __packed;
>> +
>> +/* Gather all capabilities related information info one type */
>> +typedef union {
>> +	struct	permanent_flags_t perm_flags;
>> +	struct	stclear_flags_t	stclear_flags;
>> +	__u8	owned;
>> +	__be32	num_pcrs;
>> +	struct tpm1_version version1;
>> +	struct tpm1_version2 version2;
>> +	__be32	manufacturer_id;
>> +	struct timeout_t  timeout;
>> +	struct duration_t duration;
>> +} cap_t;
> 
> Don't retain alignment here.
> 

Okay.

>> +
>> +struct tpm1_get_random_out {
>> +	__be32 rng_data_len;
>> +	u8 rng_data[TPM_MAX_RNG_DATA];
>> +} __packed;
>> +
>> +#endif
>> diff --git a/include/linux/tpm1.h b/include/linux/tpm1.h
>> index 54c6c211eb9e..5fad94ac8d15 100644
>> --- a/include/linux/tpm1.h
>> +++ b/include/linux/tpm1.h
>> @@ -47,7 +47,39 @@ enum tpm_command_ordinals {
>>   	TPM_ORD_UNSEAL			= 24,
>>   };
>>   
>> -/* Other constants */
>> +enum tpm_capabilities {
>> +	TPM_CAP_FLAG		= 4,
>> +	TPM_CAP_PROP		= 5,
>> +	TPM_CAP_VERSION_1_1	= 0x06,
>> +	TPM_CAP_VERSION_1_2	= 0x1A,
>> +};
>> +
>> +enum tpm_sub_capabilities {
>> +	TPM_CAP_PROP_PCR		= 0x101,
>> +	TPM_CAP_PROP_MANUFACTURER	= 0x103,
>> +	TPM_CAP_FLAG_PERM		= 0x108,
>> +	TPM_CAP_FLAG_VOL		= 0x109,
>> +	TPM_CAP_PROP_OWNER		= 0x111,
>> +	TPM_CAP_PROP_TIS_TIMEOUT	= 0x115,
>> +	TPM_CAP_PROP_TIS_DURATION	= 0x120,
>> +};
>> +
>> +/* Return Codes */
>> +enum tpm_return_codes {
>> +	TPM_BASE_MASK			= 0,
>> +	TPM_NON_FATAL_MASK		= 0x00000800,
>> +	TPM_SUCCESS			= TPM_BASE_MASK + 0,
>> +	TPM_ERR_DEACTIVATED		= TPM_BASE_MASK + 6,
>> +	TPM_ERR_DISABLED		= TPM_BASE_MASK + 7,
>> +	TPM_ERR_FAIL			= TPM_BASE_MASK + 9,
>> +	TPM_ERR_FAILEDSELFTEST		= TPM_BASE_MASK + 28,
>> +	TPM_ERR_INVALID_POSTINIT	= TPM_BASE_MASK + 38,
>> +	TPM_ERR_INVALID_FAMILY		= TPM_BASE_MASK + 55,
>> +	TPM_WARN_RETRY			= TPM_BASE_MASK + TPM_NON_FATAL_MASK + 0,
>> +	TPM_WARN_DOING_SELFTEST		= TPM_BASE_MASK + TPM_NON_FATAL_MASK + 2,
>> +};
>> +
>> +/* Misc. constants */
> 
> These constants should be relocated in a separate patch.
> 

Okay.

>>   #define SRKHANDLE                       0x40000000
>>   #define TPM_NONCE_SIZE                  20
>>   #define TPM_ST_CLEAR			1
>> -- 
>> 2.43.7
>>
> 
> BR, Jarkko


^ permalink raw reply

* Re: [PATCH v15 01/28] tpm: Initial step to reorganize TPM public headers
From: Daniel P. Smith @ 2026-02-01 16:21 UTC (permalink / raw)
  To: Jarkko Sakkinen, Ross Philipson
  Cc: linux-kernel, x86, linux-integrity, linux-doc, linux-crypto,
	kexec, linux-efi, iommu, tglx, mingo, bp, hpa, dave.hansen, ardb,
	mjg59, James.Bottomley, peterhuewe, jgg, luto, nivedita, herbert,
	davem, corbet, ebiederm, dwmw2, baolu.lu, kanth.ghatraju,
	andrew.cooper3, trenchboot-devel
In-Reply-To: <aW7D2ZQZmoXF0L2p@kernel.org>

On 1/19/26 18:52, Jarkko Sakkinen wrote:
> On Tue, Jan 20, 2026 at 01:40:49AM +0200, Jarkko Sakkinen wrote:
>> On Mon, Dec 15, 2025 at 03:32:49PM -0800, Ross Philipson wrote:
>>> Replace the existing public header tpm_command.h with the first two
>>> new public headers tpm1.h and tpm_common.h. In addition, related
>>> definitions in tpm1_cmd.c were moved to the new tpm1.h.
>>>
>>> Signed-off-by: Daniel P. Smith <dpsmith@apertussolutions.com>
>>> Signed-off-by: Ross Philipson <ross.philipson@oracle.com>
>>> ---
>>>   drivers/char/tpm/tpm-buf.c                |  3 +-
>>>   drivers/char/tpm/tpm1-cmd.c               | 13 +-----
>>>   include/keys/trusted_tpm.h                |  1 -
>>>   include/linux/tpm.h                       |  3 ++
>>>   include/linux/tpm1.h                      | 55 +++++++++++++++++++++++
>>>   include/linux/tpm_command.h               | 30 -------------
>>
>> Removing tpm_command.h causes unnecessary noise.
>>
>> It would be better to retain tpm_command.h, and simply supplement
>> it with TPM2 constants.
>>
>> Also, what is the reason to not have both TPM1 and TPM2 in tpm.h?
>>
>> To put the question in other words: is there something in tpm.h that
>> would be incompatible with early boot code?
>>
>> I'd rather tweak that than have more files...

Ack.

>>>   include/linux/tpm_common.h                | 22 +++++++++
>>>   security/keys/trusted-keys/trusted_tpm1.c |  1 -
>>>   security/keys/trusted-keys/trusted_tpm2.c |  1 -
>>>   9 files changed, 82 insertions(+), 47 deletions(-)
>>>   create mode 100644 include/linux/tpm1.h
>>>   delete mode 100644 include/linux/tpm_command.h
>>>   create mode 100644 include/linux/tpm_common.h
>>>
>>> diff --git a/drivers/char/tpm/tpm-buf.c b/drivers/char/tpm/tpm-buf.c
>>> index 1cb649938c01..dae23e6de269 100644
>>> --- a/drivers/char/tpm/tpm-buf.c
>>> +++ b/drivers/char/tpm/tpm-buf.c
>>> @@ -3,7 +3,6 @@
>>>    * Handling of TPM command and other buffers.
>>>    */
>>>   
>>> -#include <linux/tpm_command.h>
>>>   #include <linux/module.h>
>>>   #include <linux/tpm.h>
>>>   
>>> @@ -296,7 +295,7 @@ void tpm1_buf_append_extend(struct tpm_buf *buf, u32 pcr_idx, const u8 *hash)
>>>   	if (buf->flags & TPM_BUF_INVALID)
>>>   		return;
>>>   
>>> -	if (!tpm1_buf_is_command(buf, TPM_ORD_EXTEND)) {
>>> +	if (!tpm1_buf_is_command(buf, TPM_ORD_PCR_EXTEND)) {
>>>   		WARN(1, "tpm_buf: invalid TPM_Extend command\n");
>>>   		buf->flags |= TPM_BUF_INVALID;
>>>   		return;
>>> diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c
>>> index bc156d7d59f2..f29827b454d2 100644
>>> --- a/drivers/char/tpm/tpm1-cmd.c
>>> +++ b/drivers/char/tpm/tpm1-cmd.c
>>> @@ -18,12 +18,9 @@
>>>   #include <linux/mutex.h>
>>>   #include <linux/spinlock.h>
>>>   #include <linux/freezer.h>
>>> -#include <linux/tpm_command.h>
>>>   #include <linux/tpm_eventlog.h>
>>>   #include "tpm.h"
>>>   
>>> -#define TPM_MAX_ORDINAL 243
>>> -
>>>   /*
>>>    * Array with one entry per ordinal defining the maximum amount
>>>    * of time the chip could take to return the result.  The ordinal
>>> @@ -308,9 +305,6 @@ unsigned long tpm1_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal)
>>>   		return duration;
>>>   }
>>>   
>>> -#define TPM_ORD_STARTUP 153
>>> -#define TPM_ST_CLEAR 1
>>> -
>>>   /**
>>>    * tpm1_startup() - turn on the TPM
>>>    * @chip: TPM chip to use
>>> @@ -478,7 +472,6 @@ int tpm1_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, const u8 *hash,
>>>   	return rc;
>>>   }
>>>   
>>> -#define TPM_ORD_GET_CAP 101
>>>   ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap,
>>>   		    const char *desc, size_t min_cap_length)
>>>   {
>>> @@ -574,7 +567,6 @@ int tpm1_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
>>>   	return rc;
>>>   }
>>>   
>>> -#define TPM_ORD_PCRREAD 21
>>>   int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf)
>>>   {
>>>   	int rc;
>>> @@ -584,7 +576,7 @@ int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf)
>>>   		return -ENOMEM;
>>>   
>>>   	tpm_buf_init(buf, TPM_BUFSIZE);
>>> -	tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCRREAD);
>>> +	tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCR_READ);
>>>   	tpm_buf_append_u32(buf, pcr_idx);
>>>   
>>>   	rc = tpm_transmit_cmd(chip, buf, TPM_DIGEST_SIZE,
>>> @@ -599,7 +591,6 @@ int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf)
>>>   	return rc;
>>>   }
>>>   
>>> -#define TPM_ORD_CONTINUE_SELFTEST 83
>>>   /**
>>>    * tpm1_continue_selftest() - run TPM's selftest
>>>    * @chip: TPM chip to use
>>> @@ -716,8 +707,6 @@ int tpm1_auto_startup(struct tpm_chip *chip)
>>>   	return rc;
>>>   }
>>>   
>>> -#define TPM_ORD_SAVESTATE 152
>>> -
>>>   /**
>>>    * tpm1_pm_suspend() - pm suspend handler
>>>    * @chip: TPM chip to use.
>>> diff --git a/include/keys/trusted_tpm.h b/include/keys/trusted_tpm.h
>>> index 0fadc6a4f166..3a0fa3bc8454 100644
>>> --- a/include/keys/trusted_tpm.h
>>> +++ b/include/keys/trusted_tpm.h
>>> @@ -3,7 +3,6 @@
>>>   #define __TRUSTED_TPM_H
>>>   
>>>   #include <keys/trusted-type.h>
>>> -#include <linux/tpm_command.h>
>>>   
>>>   extern struct trusted_key_ops trusted_key_tpm_ops;
>>>   
>>> diff --git a/include/linux/tpm.h b/include/linux/tpm.h
>>> index 8da49e8769d5..ef81e0b59657 100644
>>> --- a/include/linux/tpm.h
>>> +++ b/include/linux/tpm.h
>>> @@ -25,6 +25,9 @@
>>>   #include <crypto/hash_info.h>
>>>   #include <crypto/aes.h>
>>>   
>>> +#include "tpm_common.h"
>>> +#include "tpm1.h"
>>> +
>>>   #define TPM_DIGEST_SIZE		20	/* Max TPM v1.2 PCR size */
>>>   #define TPM_HEADER_SIZE		10
>>>   #define TPM_BUFSIZE		4096
>>> diff --git a/include/linux/tpm1.h b/include/linux/tpm1.h
>>> new file mode 100644
>>> index 000000000000..54c6c211eb9e
>>> --- /dev/null
>>> +++ b/include/linux/tpm1.h
>>> @@ -0,0 +1,55 @@
>>> +/* SPDX-License-Identifier: GPL-2.0-only */
>>> +/*
>>> + * Copyright (C) 2004,2007,2008 IBM Corporation
>>> + *
>>> + * Authors:
>>> + * Leendert van Doorn <leendert@watson.ibm.com>
>>> + * Dave Safford <safford@watson.ibm.com>
>>> + * Reiner Sailer <sailer@watson.ibm.com>
>>> + * Kylene Hall <kjhall@us.ibm.com>
>>> + * Debora Velarde <dvelarde@us.ibm.com>
>>> + *
>>> + * Maintained by: <tpmdd_devel@lists.sourceforge.net>
>>> + *
>>> + * Device driver for TCG/TCPA TPM (trusted platform module).
>>> + * Specifications at www.trustedcomputinggroup.org
>>> + */
>>> +#ifndef __LINUX_TPM1_H__
>>> +#define __LINUX_TPM1_H__
>>> +
>>> +/*
>>> + * TPM 1.2 Main Specification
>>> + * https://trustedcomputinggroup.org/resource/tpm-main-specification/
>>> + */
>>> +
>>> +/* Command TAGS */
>>> +enum tpm_command_tags {
>>> +	TPM_TAG_RQU_COMMAND		= 193,
>>> +	TPM_TAG_RQU_AUTH1_COMMAND	= 194,
>>> +	TPM_TAG_RQU_AUTH2_COMMAND	= 195,
>>> +	TPM_TAG_RSP_COMMAND		= 196,
>>> +	TPM_TAG_RSP_AUTH1_COMMAND	= 197,
>>> +	TPM_TAG_RSP_AUTH2_COMMAND	= 198,
>>> +};
>>> +
>>> +/* Command Ordinals */
>>> +enum tpm_command_ordinals {
>>> +	TPM_ORD_CONTINUE_SELFTEST	= 83,
>>> +	TPM_ORD_GET_CAP			= 101,
>>> +	TPM_ORD_GET_RANDOM		= 70,
>>> +	TPM_ORD_PCR_EXTEND		= 20,
>>> +	TPM_ORD_PCR_READ		= 21,
>>> +	TPM_ORD_OSAP			= 11,
>>> +	TPM_ORD_OIAP			= 10,
>>> +	TPM_ORD_SAVESTATE		= 152,
>>> +	TPM_ORD_SEAL			= 23,
>>> +	TPM_ORD_STARTUP			= 153,
>>> +	TPM_ORD_UNSEAL			= 24,
>>> +};
>>> +
>>> +/* Other constants */
>>> +#define SRKHANDLE                       0x40000000
>>> +#define TPM_NONCE_SIZE                  20
>>> +#define TPM_ST_CLEAR			1
>>> +
>>> +#endif
>>> diff --git a/include/linux/tpm_command.h b/include/linux/tpm_command.h
>>> deleted file mode 100644
>>> index 02038972a05f..000000000000
>>> --- a/include/linux/tpm_command.h
>>> +++ /dev/null
>>> @@ -1,30 +0,0 @@
>>> -/* SPDX-License-Identifier: GPL-2.0 */
>>> -#ifndef __LINUX_TPM_COMMAND_H__
>>> -#define __LINUX_TPM_COMMAND_H__
>>> -
>>> -/*
>>> - * TPM Command constants from specifications at
>>> - * http://www.trustedcomputinggroup.org
>>> - */
>>> -
>>> -/* Command TAGS */
>>> -#define TPM_TAG_RQU_COMMAND             193
>>> -#define TPM_TAG_RQU_AUTH1_COMMAND       194
>>> -#define TPM_TAG_RQU_AUTH2_COMMAND       195
>>> -#define TPM_TAG_RSP_COMMAND             196
>>> -#define TPM_TAG_RSP_AUTH1_COMMAND       197
>>> -#define TPM_TAG_RSP_AUTH2_COMMAND       198
>>> -
>>> -/* Command Ordinals */
>>> -#define TPM_ORD_OIAP                    10
>>> -#define TPM_ORD_OSAP                    11
>>> -#define TPM_ORD_EXTEND			20
>>> -#define TPM_ORD_SEAL                    23
>>> -#define TPM_ORD_UNSEAL                  24
>>> -#define TPM_ORD_GET_RANDOM              70
>>> -
>>> -/* Other constants */
>>> -#define SRKHANDLE                       0x40000000
>>> -#define TPM_NONCE_SIZE                  20
>>> -
>>> -#endif
>>> diff --git a/include/linux/tpm_common.h b/include/linux/tpm_common.h
>>> new file mode 100644
>>> index 000000000000..b8be669913dd
>>> --- /dev/null
>>> +++ b/include/linux/tpm_common.h
>>> @@ -0,0 +1,22 @@
>>> +/* SPDX-License-Identifier: GPL-2.0-only */
>>> +/*
>>> + * Copyright (C) 2004,2007,2008 IBM Corporation
>>> + *
>>> + * Authors:
>>> + * Leendert van Doorn <leendert@watson.ibm.com>
>>> + * Dave Safford <safford@watson.ibm.com>
>>> + * Reiner Sailer <sailer@watson.ibm.com>
>>> + * Kylene Hall <kjhall@us.ibm.com>
>>> + * Debora Velarde <dvelarde@us.ibm.com>
>>> + *
>>> + * Maintained by: <tpmdd_devel@lists.sourceforge.net>
>>> + *
>>> + * Device driver for TCG/TCPA TPM (trusted platform module).
>>> + * Specifications at www.trustedcomputinggroup.org
>>> + */
>>> +#ifndef __LINUX_TPM_COMMON_H__
>>> +#define __LINUX_TPM_COMMON_H__
>>> +
>>> +#define TPM_MAX_ORDINAL 243
>>> +
>>> +#endif
> 
> By retaining tpm_command.h, this file neither won't be necessary.
> 
>>> diff --git a/security/keys/trusted-keys/trusted_tpm1.c b/security/keys/trusted-keys/trusted_tpm1.c
>>> index 6e6a9fb48e63..3717a06a5212 100644
>>> --- a/security/keys/trusted-keys/trusted_tpm1.c
>>> +++ b/security/keys/trusted-keys/trusted_tpm1.c
>>> @@ -17,7 +17,6 @@
>>>   #include <keys/trusted-type.h>
>>>   #include <linux/key-type.h>
>>>   #include <linux/tpm.h>
>>> -#include <linux/tpm_command.h>
>>>   
>>>   #include <keys/trusted_tpm.h>
>>>   
>>> diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c
>>> index 0a99bd051a25..e6000c71eeb6 100644
>>> --- a/security/keys/trusted-keys/trusted_tpm2.c
>>> +++ b/security/keys/trusted-keys/trusted_tpm2.c
>>> @@ -9,7 +9,6 @@
>>>   #include <linux/string.h>
>>>   #include <linux/err.h>
>>>   #include <linux/tpm.h>
>>> -#include <linux/tpm_command.h>
>>>   
>>>   #include <keys/trusted-type.h>
>>>   #include <keys/trusted_tpm.h>
>>> -- 
>>> 2.43.7
>>>
> 
> BR, Jarkko

V/r,
DPS

^ permalink raw reply

* Re: [PATCH v4 00/17] module: Introduce hash-based integrity checking
From: Thomas Weißschuh @ 2026-02-01 16:22 UTC (permalink / raw)
  To: Mihai-Drosi Câju
  Cc: arnd, arnout, atomlin, bigeasy, chleroy, christian, corbet, coxu,
	da.gomez, da.gomez, dmitry.kasatkin, eric.snowberg,
	f.gruenbichler, jmorris, kpcyrd, linux-arch, linux-doc,
	linux-integrity, linux-kbuild, linux-kernel, linux-modules,
	linux-security-module, linuxppc-dev, lkp, maddy, mattia, mcgrof,
	mpe, nathan, naveen, nicolas.bouchinet, nicolas.schier, npiggin,
	nsc, paul, petr.pavlu, roberto.sassu, samitolvanen, serge,
	xiujianfeng, zohar
In-Reply-To: <20260131073636.65494-1-mcaju95@gmail.com>

Hi Mihai-Drosi,

thanks for taking an interest into these patches!

On 2026-01-31 09:36:36+0200, Mihai-Drosi Câju wrote:
> > The current signature-based module integrity checking has some drawbacks
> > in combination with reproducible builds. Either the module signing key
> > is generated at build time, which makes the build unreproducible, or a
> > static signing key is used, which precludes rebuilds by third parties
> > and makes the whole build and packaging process much more complicated.
> 
> I think there is a middle ground where the module signing key is generated
> using a key derivation function that has as an input a deterministic value
> on the build host, such as /etc/machine-id . The problem with this approach
> is that only hosts knowing the value will be able to reproduce the build.

The goal is to make the distro kernel packages rebuildable by the
general public. Any involvement of secret values will break this goal.

> Maybe this is a solution to NixOS secret management? Introduce minimal
> impurity as a cryptographic seed and derive the rest of the secrets using
> something like Argon2(seed, key_uuid).

I am not familiar with NixOS and its secret management.
This patchset serves a wider audience.

> There might be another approach to code integrity rather than step-by-step
> reproducibility. One may exploit the very cryptographic primitives that make
> reproducibility hard to ensure that reproducibility is most  likely valid.
> 
> For example, the module signing issue, the build host publishes four artifacts:
> * The source-code
> * The compiled and signed binary
> * The build environment
> * Its public key
> 
> Now, we don't need to sign with the private key to know that building the source
> code using the specific build environment and signing the result with the private
> key will result in the claimed binary. We can just compile and verify with the
> public key.

This could work if the goal is only to verify the reproducibility of a
single, signed-en-bloc artifact. But we also need to handle vmlinux which
contains the corresponding public key. It would need different handling.
We can add some special logic to strip that public key before
comparision. But then vmlinux might be compressed or wrapped in some
other format. Another whole collection of special logic.

(...)


Thomas

^ permalink raw reply

* Re: [PATCH v15 01/28] tpm: Initial step to reorganize TPM public headers
From: Daniel P. Smith @ 2026-02-01 16:20 UTC (permalink / raw)
  To: Jarkko Sakkinen, Ross Philipson
  Cc: linux-kernel, x86, linux-integrity, linux-doc, linux-crypto,
	kexec, linux-efi, iommu, tglx, mingo, bp, hpa, dave.hansen, ardb,
	mjg59, James.Bottomley, peterhuewe, jgg, luto, nivedita, herbert,
	davem, corbet, ebiederm, dwmw2, baolu.lu, kanth.ghatraju,
	andrew.cooper3, trenchboot-devel
In-Reply-To: <aW7A-4xJSzln1HtH@kernel.org>

On 1/19/26 18:40, Jarkko Sakkinen wrote:
> On Mon, Dec 15, 2025 at 03:32:49PM -0800, Ross Philipson wrote:
>> Replace the existing public header tpm_command.h with the first two
>> new public headers tpm1.h and tpm_common.h. In addition, related
>> definitions in tpm1_cmd.c were moved to the new tpm1.h.
>>
>> Signed-off-by: Daniel P. Smith <dpsmith@apertussolutions.com>
>> Signed-off-by: Ross Philipson <ross.philipson@oracle.com>
>> ---
>>   drivers/char/tpm/tpm-buf.c                |  3 +-
>>   drivers/char/tpm/tpm1-cmd.c               | 13 +-----
>>   include/keys/trusted_tpm.h                |  1 -
>>   include/linux/tpm.h                       |  3 ++
>>   include/linux/tpm1.h                      | 55 +++++++++++++++++++++++
>>   include/linux/tpm_command.h               | 30 -------------
> 
> Removing tpm_command.h causes unnecessary noise.
> 
> It would be better to retain tpm_command.h, and simply supplement
> it with TPM2 constants.
> 
> Also, what is the reason to not have both TPM1 and TPM2 in tpm.h?
> 
> To put the question in other words: is there something in tpm.h that
> would be incompatible with early boot code?
> 
> I'd rather tweak that than have more files...

Every #include in tpm.h will break in the early boot code. I don't see 
any way to avoid having one header that is the device driver header that 
integrates with mainline features and at least one header that holds the 
general TPM definitions.

We will move everything that was broken out into tpm_command.h, making 
it the header with the general definitions. I would raise the question 
of whether tpm_command.h would be the best name of the file after 
definition reloactions

>>   include/linux/tpm_common.h                | 22 +++++++++
>>   security/keys/trusted-keys/trusted_tpm1.c |  1 -
>>   security/keys/trusted-keys/trusted_tpm2.c |  1 -
>>   9 files changed, 82 insertions(+), 47 deletions(-)
>>   create mode 100644 include/linux/tpm1.h
>>   delete mode 100644 include/linux/tpm_command.h
>>   create mode 100644 include/linux/tpm_common.h
>>
>> diff --git a/drivers/char/tpm/tpm-buf.c b/drivers/char/tpm/tpm-buf.c
>> index 1cb649938c01..dae23e6de269 100644
>> --- a/drivers/char/tpm/tpm-buf.c
>> +++ b/drivers/char/tpm/tpm-buf.c
>> @@ -3,7 +3,6 @@
>>    * Handling of TPM command and other buffers.
>>    */
>>   
>> -#include <linux/tpm_command.h>
>>   #include <linux/module.h>
>>   #include <linux/tpm.h>
>>   
>> @@ -296,7 +295,7 @@ void tpm1_buf_append_extend(struct tpm_buf *buf, u32 pcr_idx, const u8 *hash)
>>   	if (buf->flags & TPM_BUF_INVALID)
>>   		return;
>>   
>> -	if (!tpm1_buf_is_command(buf, TPM_ORD_EXTEND)) {
>> +	if (!tpm1_buf_is_command(buf, TPM_ORD_PCR_EXTEND)) {
>>   		WARN(1, "tpm_buf: invalid TPM_Extend command\n");
>>   		buf->flags |= TPM_BUF_INVALID;
>>   		return;
>> diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c
>> index bc156d7d59f2..f29827b454d2 100644
>> --- a/drivers/char/tpm/tpm1-cmd.c
>> +++ b/drivers/char/tpm/tpm1-cmd.c
>> @@ -18,12 +18,9 @@
>>   #include <linux/mutex.h>
>>   #include <linux/spinlock.h>
>>   #include <linux/freezer.h>
>> -#include <linux/tpm_command.h>
>>   #include <linux/tpm_eventlog.h>
>>   #include "tpm.h"
>>   
>> -#define TPM_MAX_ORDINAL 243
>> -
>>   /*
>>    * Array with one entry per ordinal defining the maximum amount
>>    * of time the chip could take to return the result.  The ordinal
>> @@ -308,9 +305,6 @@ unsigned long tpm1_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal)
>>   		return duration;
>>   }
>>   
>> -#define TPM_ORD_STARTUP 153
>> -#define TPM_ST_CLEAR 1
>> -
>>   /**
>>    * tpm1_startup() - turn on the TPM
>>    * @chip: TPM chip to use
>> @@ -478,7 +472,6 @@ int tpm1_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, const u8 *hash,
>>   	return rc;
>>   }
>>   
>> -#define TPM_ORD_GET_CAP 101
>>   ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap,
>>   		    const char *desc, size_t min_cap_length)
>>   {
>> @@ -574,7 +567,6 @@ int tpm1_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
>>   	return rc;
>>   }
>>   
>> -#define TPM_ORD_PCRREAD 21
>>   int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf)
>>   {
>>   	int rc;
>> @@ -584,7 +576,7 @@ int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf)
>>   		return -ENOMEM;
>>   
>>   	tpm_buf_init(buf, TPM_BUFSIZE);
>> -	tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCRREAD);
>> +	tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCR_READ);
>>   	tpm_buf_append_u32(buf, pcr_idx);
>>   
>>   	rc = tpm_transmit_cmd(chip, buf, TPM_DIGEST_SIZE,
>> @@ -599,7 +591,6 @@ int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf)
>>   	return rc;
>>   }
>>   
>> -#define TPM_ORD_CONTINUE_SELFTEST 83
>>   /**
>>    * tpm1_continue_selftest() - run TPM's selftest
>>    * @chip: TPM chip to use
>> @@ -716,8 +707,6 @@ int tpm1_auto_startup(struct tpm_chip *chip)
>>   	return rc;
>>   }
>>   
>> -#define TPM_ORD_SAVESTATE 152
>> -
>>   /**
>>    * tpm1_pm_suspend() - pm suspend handler
>>    * @chip: TPM chip to use.
>> diff --git a/include/keys/trusted_tpm.h b/include/keys/trusted_tpm.h
>> index 0fadc6a4f166..3a0fa3bc8454 100644
>> --- a/include/keys/trusted_tpm.h
>> +++ b/include/keys/trusted_tpm.h
>> @@ -3,7 +3,6 @@
>>   #define __TRUSTED_TPM_H
>>   
>>   #include <keys/trusted-type.h>
>> -#include <linux/tpm_command.h>
>>   
>>   extern struct trusted_key_ops trusted_key_tpm_ops;
>>   
>> diff --git a/include/linux/tpm.h b/include/linux/tpm.h
>> index 8da49e8769d5..ef81e0b59657 100644
>> --- a/include/linux/tpm.h
>> +++ b/include/linux/tpm.h
>> @@ -25,6 +25,9 @@
>>   #include <crypto/hash_info.h>
>>   #include <crypto/aes.h>
>>   
>> +#include "tpm_common.h"
>> +#include "tpm1.h"
>> +
>>   #define TPM_DIGEST_SIZE		20	/* Max TPM v1.2 PCR size */
>>   #define TPM_HEADER_SIZE		10
>>   #define TPM_BUFSIZE		4096
>> diff --git a/include/linux/tpm1.h b/include/linux/tpm1.h
>> new file mode 100644
>> index 000000000000..54c6c211eb9e
>> --- /dev/null
>> +++ b/include/linux/tpm1.h
>> @@ -0,0 +1,55 @@
>> +/* SPDX-License-Identifier: GPL-2.0-only */
>> +/*
>> + * Copyright (C) 2004,2007,2008 IBM Corporation
>> + *
>> + * Authors:
>> + * Leendert van Doorn <leendert@watson.ibm.com>
>> + * Dave Safford <safford@watson.ibm.com>
>> + * Reiner Sailer <sailer@watson.ibm.com>
>> + * Kylene Hall <kjhall@us.ibm.com>
>> + * Debora Velarde <dvelarde@us.ibm.com>
>> + *
>> + * Maintained by: <tpmdd_devel@lists.sourceforge.net>
>> + *
>> + * Device driver for TCG/TCPA TPM (trusted platform module).
>> + * Specifications at www.trustedcomputinggroup.org
>> + */
>> +#ifndef __LINUX_TPM1_H__
>> +#define __LINUX_TPM1_H__
>> +
>> +/*
>> + * TPM 1.2 Main Specification
>> + * https://trustedcomputinggroup.org/resource/tpm-main-specification/
>> + */
>> +
>> +/* Command TAGS */
>> +enum tpm_command_tags {
>> +	TPM_TAG_RQU_COMMAND		= 193,
>> +	TPM_TAG_RQU_AUTH1_COMMAND	= 194,
>> +	TPM_TAG_RQU_AUTH2_COMMAND	= 195,
>> +	TPM_TAG_RSP_COMMAND		= 196,
>> +	TPM_TAG_RSP_AUTH1_COMMAND	= 197,
>> +	TPM_TAG_RSP_AUTH2_COMMAND	= 198,
>> +};
>> +
>> +/* Command Ordinals */
>> +enum tpm_command_ordinals {
>> +	TPM_ORD_CONTINUE_SELFTEST	= 83,
>> +	TPM_ORD_GET_CAP			= 101,
>> +	TPM_ORD_GET_RANDOM		= 70,
>> +	TPM_ORD_PCR_EXTEND		= 20,
>> +	TPM_ORD_PCR_READ		= 21,
>> +	TPM_ORD_OSAP			= 11,
>> +	TPM_ORD_OIAP			= 10,
>> +	TPM_ORD_SAVESTATE		= 152,
>> +	TPM_ORD_SEAL			= 23,
>> +	TPM_ORD_STARTUP			= 153,
>> +	TPM_ORD_UNSEAL			= 24,
>> +};
>> +
>> +/* Other constants */to address your concern over diff churn
>> +#define SRKHANDLE                       0x40000000
>> +#define TPM_NONCE_SIZE                  20
>> +#define TPM_ST_CLEAR			1
>> +
>> +#endif
>> diff --git a/include/linux/tpm_command.h b/include/linux/tpm_command.h
>> deleted file mode 100644
>> index 02038972a05f..000000000000
>> --- a/include/linux/tpm_command.h
>> +++ /dev/null
>> @@ -1,30 +0,0 @@
>> -/* SPDX-License-Identifier: GPL-2.0 */
>> -#ifndef __LINUX_TPM_COMMAND_H__
>> -#define __LINUX_TPM_COMMAND_H__
>> -
>> -/*
>> - * TPM Command constants from specifications at
>> - * http://www.trustedcomputinggroup.org
>> - */
>> -
>> -/* Command TAGS */
>> -#define TPM_TAG_RQU_COMMAND             193
>> -#define TPM_TAG_RQU_AUTH1_COMMAND       194
>> -#define TPM_TAG_RQU_AUTH2_COMMAND       195
>> -#define TPM_TAG_RSP_COMMAND             196
>> -#define TPM_TAG_RSP_AUTH1_COMMAND       197
>> -#define TPM_TAG_RSP_AUTH2_COMMAND       198
>> -
>> -/* Command Ordinals */
>> -#define TPM_ORD_OIAP                    10
>> -#define TPM_ORD_OSAP                    11
>> -#define TPM_ORD_EXTEND			20
>> -#define TPM_ORD_SEAL                    23
>> -#define TPM_ORD_UNSEAL                  24
>> -#define TPM_ORD_GET_RANDOM              70
>> -
>> -/* Other constants */
>> -#define SRKHANDLE                       0x40000000
>> -#define TPM_NONCE_SIZE                  20
>> -
>> -#endif
>> diff --git a/include/linux/tpm_common.h b/include/linux/tpm_common.h
>> new file mode 100644
>> index 000000000000..b8be669913dd
>> --- /dev/null
>> +++ b/include/linux/tpm_common.h
>> @@ -0,0 +1,22 @@
>> +/* SPDX-License-Identifier: GPL-2.0-only */
>> +/*
>> + * Copyright (C) 2004,2007,2008 IBM Corporation
>> + *
>> + * Authors:
>> + * Leendert van Doorn <leendert@watson.ibm.com>
>> + * Dave Safford <safford@watson.ibm.com>
>> + * Reiner Sailer <sailer@watson.ibm.com>
>> + * Kylene Hall <kjhall@us.ibm.com>
>> + * Debora Velarde <dvelarde@us.ibm.com>
>> + *
>> + * Maintained by: <tpmdd_devel@lists.sourceforge.net>
>> + *
>> + * Device driver for TCG/TCPA TPM (trusted platform module).
>> + * Specifications at www.trustedcomputinggroup.org
>> + */
>> +#ifndef __LINUX_TPM_COMMON_H__
>> +#define __LINUX_TPM_COMMON_H__
>> +
>> +#define TPM_MAX_ORDINAL 243
>> +
>> +#endif
>> diff --git a/security/keys/trusted-keys/trusted_tpm1.c b/security/keys/trusted-keys/trusted_tpm1.c
>> index 6e6a9fb48e63..3717a06a5212 100644
>> --- a/security/keys/trusted-keys/trusted_tpm1.c
>> +++ b/security/keys/trusted-keys/trusted_tpm1.c
>> @@ -17,7 +17,6 @@
>>   #include <keys/trusted-type.h>
>>   #include <linux/key-type.h>
>>   #include <linux/tpm.h>
>> -#include <linux/tpm_command.h>
>>   
>>   #include <keys/trusted_tpm.h>
>>   
>> diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c
>> index 0a99bd051a25..e6000c71eeb6 100644
>> --- a/security/keys/trusted-keys/trusted_tpm2.c
>> +++ b/security/keys/trusted-keys/trusted_tpm2.c
>> @@ -9,7 +9,6 @@
>>   #include <linux/string.h>
>>   #include <linux/err.h>
>>   #include <linux/tpm.h>
>> -#include <linux/tpm_command.h>
>>   
>>   #include <keys/trusted-type.h>
>>   #include <keys/trusted_tpm.h>
>> -- 
>> 2.43.7
>>
> 
> BR, Jarkko

V/r,
DPS


^ permalink raw reply

* Re: [PATCH v6 0/6] Extend "trusted" keys to support a new trust source named the PowerVM Key Wrapping Module (PKWM)
From: Srish Srinivasan @ 2026-02-01 15:19 UTC (permalink / raw)
  To: linux-integrity, keyrings, linuxppc-dev
  Cc: maddy, mpe, npiggin, christophe.leroy, James.Bottomley, jarkko,
	zohar, nayna, rnsastry, linux-kernel, linux-security-module
In-Reply-To: <20260201135930.898721-1-ssrish@linux.ibm.com>

Please ignore this series (v6).

thanks,
Srish.

On 2/1/26 7:29 PM, Srish Srinivasan wrote:
> Power11 has introduced a feature called the PowerVM Key Wrapping Module
> (PKWM), where PowerVM in combination with Power LPAR Platform KeyStore
> (PLPKS) [1] supports a new feature called "Key Wrapping" [2] to protect
> user secrets by wrapping them using a hypervisor generated wrapping key.
> This wrapping key is an AES-GCM-256 symmetric key that is stored as an
> object in the PLPKS. It has policy based protections that prevents it from
> being read out or exposed to the user. This wrapping key can then be used
> by the OS to wrap or unwrap secrets via hypervisor calls.
>
> This patchset intends to add the PKWM, which is a combination of IBM
> PowerVM and PLPKS, as a new trust source for trusted keys. The wrapping key
> does not exist by default and its generation is requested by the kernel at
> the time of PKWM initialization. This key is then persisted by the PKWM and
> is used for wrapping any kernel provided key, and is never exposed to the
> user. The kernel is aware of only the label to this wrapping key.
>
> Along with the PKWM implementation, this patchset includes two preparatory
> patches: one fixing the kernel-doc inconsistencies in the PLPKS code and
> another reorganizing PLPKS config variables in the sysfs.
>
> Changelog:
>
> v6:
>
> * Patch 1 to Patch 3:
>    - Add Nayna's Tested-by tag
> * Patch 4
>    - Fix build error reported by kernel test robot <lkp@intel.com>
>    - Add Nayna's Tested-by tag
> * Patch 5
>    - Add Nayna's Tested-by tag
>
> v5:
>
> * Patch 1 to Patch 3:
>    - Add Nayna's Reviewed-by tag
> * Patch 4:
>    - Fix build error identified by chleroy@kernel.org
>    - Add Nayna's Reviewed-by tag
> * Patch 5:
>    - Add Reviewed-by tags from Nayna and Jarkko
>
> v4:
>
> * Patch 5:
>    - Add a per-backend private data pointer in trusted_key_options
>      to store a pointer to the backend-specific options structure
>    - Minor clean-up
>
> v3:
>
> * Patch 2:
>    - Add Mimi's Reviewed-by tag
> * Patch 4:
>    - Minor tweaks to some print statements
>    - Fix typos
> * Patch 5:
>    - Fix typos
>    - Add Mimi's Reviewed-by tag
> * Patch 6:
>    - Add Mimi's Reviewed-by tag
>
> v2:
>
> * Patch 2:
>    - Fix build warning detected by the kernel test bot
> * Patch 5:
>    - Use pr_debug inside dump_options
>    - Replace policyhande with wrap_flags inside dump_options
>    - Provide meaningful error messages with error codes
>
> Nayna Jain (1):
>    docs: trusted-encryped: add PKWM as a new trust source
>
> Srish Srinivasan (5):
>    pseries/plpks: fix kernel-doc comment inconsistencies
>    powerpc/pseries: move the PLPKS config inside its own sysfs directory
>    pseries/plpks: expose PowerVM wrapping features via the sysfs
>    pseries/plpks: add HCALLs for PowerVM Key Wrapping Module
>    keys/trusted_keys: establish PKWM as a trusted source
>
>   .../ABI/testing/sysfs-firmware-plpks          |  58 ++
>   Documentation/ABI/testing/sysfs-secvar        |  65 --
>   .../admin-guide/kernel-parameters.txt         |   1 +
>   Documentation/arch/powerpc/papr_hcalls.rst    |  43 ++
>   .../security/keys/trusted-encrypted.rst       |  50 ++
>   MAINTAINERS                                   |   9 +
>   arch/powerpc/include/asm/hvcall.h             |   4 +-
>   arch/powerpc/include/asm/plpks.h              |  95 +--
>   arch/powerpc/include/asm/secvar.h             |   1 -
>   arch/powerpc/kernel/secvar-sysfs.c            |  21 +-
>   arch/powerpc/platforms/pseries/Makefile       |   2 +-
>   arch/powerpc/platforms/pseries/plpks-secvar.c |  29 -
>   arch/powerpc/platforms/pseries/plpks-sysfs.c  |  96 +++
>   arch/powerpc/platforms/pseries/plpks.c        | 688 +++++++++++++++++-
>   include/keys/trusted-type.h                   |   7 +-
>   include/keys/trusted_pkwm.h                   |  33 +
>   security/keys/trusted-keys/Kconfig            |   8 +
>   security/keys/trusted-keys/Makefile           |   2 +
>   security/keys/trusted-keys/trusted_core.c     |   6 +-
>   security/keys/trusted-keys/trusted_pkwm.c     | 190 +++++
>   20 files changed, 1207 insertions(+), 201 deletions(-)
>   create mode 100644 Documentation/ABI/testing/sysfs-firmware-plpks
>   create mode 100644 arch/powerpc/platforms/pseries/plpks-sysfs.c
>   create mode 100644 include/keys/trusted_pkwm.h
>   create mode 100644 security/keys/trusted-keys/trusted_pkwm.c
>

^ permalink raw reply

* [PATCH v6 4/6] pseries/plpks: add HCALLs for PowerVM Key Wrapping Module
From: Srish Srinivasan @ 2026-02-01 13:59 UTC (permalink / raw)
  To: linux-integrity, keyrings, linuxppc-dev
  Cc: maddy, mpe, npiggin, christophe.leroy, James.Bottomley, jarkko,
	zohar, nayna, rnsastry, linux-kernel, linux-security-module,
	ssrish
In-Reply-To: <20260201135930.898721-1-ssrish@linux.ibm.com>

The hypervisor generated wrapping key is an AES-GCM-256 symmetric key which
is stored in a non-volatile, secure, and encrypted storage called the Power
LPAR Platform KeyStore. It has policy based protections that prevent it
from being read out or exposed to the user.

Implement H_PKS_GEN_KEY, H_PKS_WRAP_OBJECT, and H_PKS_UNWRAP_OBJECT HCALLs
to enable using the PowerVM Key Wrapping Module (PKWM) as a new trust
source for trusted keys. Disallow H_PKS_READ_OBJECT, H_PKS_SIGNED_UPDATE,
and H_PKS_WRITE_OBJECT for objects with the 'wrapping key' policy set.
Capture the availability status for the H_PKS_WRAP_OBJECT interface.

Signed-off-by: Srish Srinivasan <ssrish@linux.ibm.com>
Reviewed-by: Nayna Jain <nayna@linux.ibm.com>
Tested-by: Nayna Jain <nayna@linux.ibm.com>
---
 Documentation/arch/powerpc/papr_hcalls.rst |  43 +++
 arch/powerpc/include/asm/plpks.h           |  10 +
 arch/powerpc/platforms/pseries/plpks.c     | 344 ++++++++++++++++++++-
 3 files changed, 395 insertions(+), 2 deletions(-)

diff --git a/Documentation/arch/powerpc/papr_hcalls.rst b/Documentation/arch/powerpc/papr_hcalls.rst
index 805e1cb9bab9..14e39f095a1c 100644
--- a/Documentation/arch/powerpc/papr_hcalls.rst
+++ b/Documentation/arch/powerpc/papr_hcalls.rst
@@ -300,6 +300,49 @@ H_HTM supports setup, configuration, control and dumping of Hardware Trace
 Macro (HTM) function and its data. HTM buffer stores tracing data for functions
 like core instruction, core LLAT and nest.
 
+**H_PKS_GEN_KEY**
+
+| Input: authorization, objectlabel, objectlabellen, policy, out, outlen
+| Out: *Hypervisor Generated Key, or None when the wrapping key policy is set*
+| Return Value: *H_SUCCESS, H_Function, H_State, H_R_State, H_Parameter, H_P2,
+                H_P3, H_P4, H_P5, H_P6, H_Authority, H_Nomem, H_Busy, H_Resource,
+                H_Aborted*
+
+H_PKS_GEN_KEY is used to have the hypervisor generate a new random key.
+This key is stored as an object in the Power LPAR Platform KeyStore with
+the provided object label. With the wrapping key policy set the key is only
+visible to the hypervisor, while the key's label would still be visible to
+the user. Generation of wrapping keys is supported only for a key size of
+32 bytes.
+
+**H_PKS_WRAP_OBJECT**
+
+| Input: authorization, wrapkeylabel, wrapkeylabellen, objectwrapflags, in,
+|        inlen, out, outlen, continue-token
+| Out: *continue-token, byte size of wrapped object, wrapped object*
+| Return Value: *H_SUCCESS, H_Function, H_State, H_R_State, H_Parameter, H_P2,
+                H_P3, H_P4, H_P5, H_P6, H_P7, H_P8, H_P9, H_Authority, H_Invalid_Key,
+                H_NOT_FOUND, H_Busy, H_LongBusy, H_Aborted*
+
+H_PKS_WRAP_OBJECT is used to wrap an object using a wrapping key stored in the
+Power LPAR Platform KeyStore and return the wrapped object to the caller. The
+caller provides a label to a wrapping key with the 'wrapping key' policy set,
+which must have been previously created with H_PKS_GEN_KEY. The provided object
+is then encrypted with the wrapping key and additional metadata and the result
+is returned to the caller.
+
+
+**H_PKS_UNWRAP_OBJECT**
+
+| Input: authorization, objectwrapflags, in, inlen, out, outlen, continue-token
+| Out: *continue-token, byte size of unwrapped object, unwrapped object*
+| Return Value: *H_SUCCESS, H_Function, H_State, H_R_State, H_Parameter, H_P2,
+                H_P3, H_P4, H_P5, H_P6, H_P7, H_Authority, H_Unsupported, H_Bad_Data,
+                H_NOT_FOUND, H_Invalid_Key, H_Busy, H_LongBusy, H_Aborted*
+
+H_PKS_UNWRAP_OBJECT is used to unwrap an object that was previously warapped with
+H_PKS_WRAP_OBJECT.
+
 References
 ==========
 .. [1] "Power Architecture Platform Reference"
diff --git a/arch/powerpc/include/asm/plpks.h b/arch/powerpc/include/asm/plpks.h
index 8f034588fdf7..e87f90e40d4e 100644
--- a/arch/powerpc/include/asm/plpks.h
+++ b/arch/powerpc/include/asm/plpks.h
@@ -113,6 +113,16 @@ void plpks_early_init_devtree(void);
 int plpks_populate_fdt(void *fdt);
 
 int plpks_config_create_softlink(struct kobject *from);
+
+bool plpks_wrapping_is_supported(void);
+
+int plpks_gen_wrapping_key(void);
+
+int plpks_wrap_object(u8 **input_buf, u32 input_len, u16 wrap_flags,
+		      u8 **output_buf, u32 *output_len);
+
+int plpks_unwrap_object(u8 **input_buf, u32 input_len,
+			u8 **output_buf, u32 *output_len);
 #else // CONFIG_PSERIES_PLPKS
 static inline bool plpks_is_available(void) { return false; }
 static inline u16 plpks_get_passwordlen(void) { BUILD_BUG(); }
diff --git a/arch/powerpc/platforms/pseries/plpks.c b/arch/powerpc/platforms/pseries/plpks.c
index 4a08f51537c8..23e4e2a922fc 100644
--- a/arch/powerpc/platforms/pseries/plpks.c
+++ b/arch/powerpc/platforms/pseries/plpks.c
@@ -9,6 +9,32 @@
 
 #define pr_fmt(fmt) "plpks: " fmt
 
+#define PLPKS_WRAPKEY_COMPONENT	"PLPKSWR"
+#define PLPKS_WRAPKEY_NAME	"default-wrapping-key"
+
+/*
+ * To 4K align the {input, output} buffers to the {UN}WRAP H_CALLs
+ */
+#define PLPKS_WRAPPING_BUF_ALIGN	4096
+
+/*
+ * To ensure the output buffer's length is at least 1024 bytes greater
+ * than the input buffer's length during the WRAP H_CALL
+ */
+#define PLPKS_WRAPPING_BUF_DIFF	1024
+
+#define PLPKS_WRAP_INTERFACE_BIT	3
+#define PLPKS_WRAPPING_KEY_LENGTH	32
+
+#define WRAPFLAG_BE_BIT_SET(be_bit) \
+	BIT_ULL(63 - (be_bit))
+
+#define WRAPFLAG_BE_GENMASK(be_bit_hi, be_bit_lo) \
+	GENMASK_ULL(63 - (be_bit_hi), 63 - (be_bit_lo))
+
+#define WRAPFLAG_BE_FIELD_PREP(be_bit_hi, be_bit_lo, val) \
+	FIELD_PREP(WRAPFLAG_BE_GENMASK(be_bit_hi, be_bit_lo), (val))
+
 #include <linux/delay.h>
 #include <linux/errno.h>
 #include <linux/io.h>
@@ -19,6 +45,7 @@
 #include <linux/of_fdt.h>
 #include <linux/libfdt.h>
 #include <linux/memblock.h>
+#include <linux/bitfield.h>
 #include <asm/hvcall.h>
 #include <asm/machdep.h>
 #include <asm/plpks.h>
@@ -39,6 +66,7 @@ static u32 supportedpolicies;
 static u32 maxlargeobjectsize;
 static u64 signedupdatealgorithms;
 static u64 wrappingfeatures;
+static bool wrapsupport;
 
 struct plpks_auth {
 	u8 version;
@@ -283,6 +311,7 @@ static int _plpks_get_config(void)
 	maxlargeobjectsize = be32_to_cpu(config->maxlargeobjectsize);
 	signedupdatealgorithms = be64_to_cpu(config->signedupdatealgorithms);
 	wrappingfeatures = be64_to_cpu(config->wrappingfeatures);
+	wrapsupport = config->flags & PPC_BIT8(PLPKS_WRAP_INTERFACE_BIT);
 
 	// Validate that the numbers we get back match the requirements of the spec
 	if (maxpwsize < 32) {
@@ -614,6 +643,9 @@ int plpks_signed_update_var(struct plpks_var *var, u64 flags)
 	if (!(var->policy & PLPKS_SIGNEDUPDATE))
 		return -EINVAL;
 
+	if (var->policy & PLPKS_WRAPPINGKEY)
+		return -EINVAL;
+
 	// Signed updates need the component to be NULL.
 	if (var->component)
 		return -EINVAL;
@@ -696,6 +728,9 @@ int plpks_write_var(struct plpks_var var)
 	if (var.policy & PLPKS_SIGNEDUPDATE)
 		return -EINVAL;
 
+	if (var.policy & PLPKS_WRAPPINGKEY)
+		return -EINVAL;
+
 	auth = construct_auth(PLPKS_OS_OWNER);
 	if (IS_ERR(auth))
 		return PTR_ERR(auth);
@@ -790,6 +825,9 @@ static int plpks_read_var(u8 consumer, struct plpks_var *var)
 	if (var->namelen > PLPKS_MAX_NAME_SIZE)
 		return -EINVAL;
 
+	if (var->policy & PLPKS_WRAPPINGKEY)
+		return -EINVAL;
+
 	auth = construct_auth(consumer);
 	if (IS_ERR(auth))
 		return PTR_ERR(auth);
@@ -845,8 +883,310 @@ static int plpks_read_var(u8 consumer, struct plpks_var *var)
 }
 
 /**
- * plpks_read_os_var() - Fetch the data for the specified variable that is
- * owned by the OS consumer.
+ * plpks_wrapping_is_supported() - Get the H_PKS_WRAP_OBJECT interface
+ * availability status for the LPAR.
+ *
+ * Successful execution of the H_PKS_GET_CONFIG HCALL during initialization
+ * sets bit 3 of the flags variable in the PLPKS config structure if the
+ * H_PKS_WRAP_OBJECT interface is supported.
+ *
+ * Returns: true if the H_PKS_WRAP_OBJECT interface is supported, false if not.
+ */
+bool plpks_wrapping_is_supported(void)
+{
+	return wrapsupport;
+}
+EXPORT_SYMBOL_GPL(plpks_wrapping_is_supported);
+
+/**
+ * plpks_gen_wrapping_key() - Generate a new random key with the 'wrapping key'
+ * policy set.
+ *
+ * The H_PKS_GEN_KEY HCALL makes the hypervisor generate a new random key and
+ * store the key in a PLPKS object with the provided object label. With the
+ * 'wrapping key' policy set, only the label to the newly generated random key
+ * would be visible to the user.
+ *
+ * Possible reasons for the returned errno values:
+ *
+ * -ENXIO	if PLPKS is not supported
+ * -EIO		if PLPKS access is blocked due to the LPAR's state
+ *		if PLPKS modification is blocked due to the LPAR's state
+ *		if an error occurred while processing the request
+ * -EINVAL	if invalid authorization parameter
+ *		if invalid object label parameter
+ *		if invalid object label len parameter
+ *		if invalid or unsupported policy declaration
+ *		if invalid output buffer parameter
+ *		if invalid output buffer length parameter
+ * -EPERM	if access is denied
+ * -ENOMEM	if there is inadequate memory to perform this operation
+ * -EBUSY	if unable to handle the request
+ * -EEXIST	if the object label already exists
+ *
+ * Returns: On success 0 is returned, a negative errno if not.
+ */
+int plpks_gen_wrapping_key(void)
+{
+	unsigned long retbuf[PLPAR_HCALL_BUFSIZE] = { 0 };
+	struct plpks_auth *auth;
+	struct label *label;
+	int rc = 0, pseries_status = 0;
+	struct plpks_var var = {
+		.name = PLPKS_WRAPKEY_NAME,
+		.namelen = strlen(var.name),
+		.policy = PLPKS_WRAPPINGKEY,
+		.os = PLPKS_VAR_LINUX,
+		.component = PLPKS_WRAPKEY_COMPONENT
+	};
+
+	auth = construct_auth(PLPKS_OS_OWNER);
+	if (IS_ERR(auth))
+		return PTR_ERR(auth);
+
+	label = construct_label(var.component, var.os, var.name, var.namelen);
+	if (IS_ERR(label)) {
+		rc = PTR_ERR(label);
+		goto out;
+	}
+
+	rc = plpar_hcall(H_PKS_GEN_KEY, retbuf,
+			 virt_to_phys(auth), virt_to_phys(label),
+			 label->size, var.policy,
+			 NULL, PLPKS_WRAPPING_KEY_LENGTH);
+
+	if (!rc)
+		rc = plpks_confirm_object_flushed(label, auth);
+
+	pseries_status = rc;
+	rc = pseries_status_to_err(rc);
+
+	if (rc && rc != -EEXIST) {
+		pr_err("H_PKS_GEN_KEY failed. pseries_status=%d, rc=%d",
+		       pseries_status, rc);
+	} else {
+		rc = 0;
+	}
+
+	kfree(label);
+out:
+	kfree(auth);
+	return rc;
+}
+EXPORT_SYMBOL_GPL(plpks_gen_wrapping_key);
+
+/**
+ * plpks_wrap_object() - Wrap an object using the default wrapping key stored in
+ * the PLPKS.
+ * @input_buf: buffer containing the data to be wrapped
+ * @input_len: length of the input buffer
+ * @wrap_flags: object wrapping flags
+ * @output_buf: buffer to store the wrapped data
+ * @output_len: length of the output buffer
+ *
+ * The H_PKS_WRAP_OBJECT HCALL wraps an object using a wrapping key stored in
+ * the PLPKS and returns the wrapped object to the caller. The caller provides a
+ * label to the wrapping key with the 'wrapping key' policy set that must have
+ * been previously created with the H_PKS_GEN_KEY HCALL. The provided object is
+ * then encrypted with the wrapping key and additional metadata and the result
+ * is returned to the user. The metadata includes the wrapping algorithm and the
+ * wrapping key name so those parameters are not required during unwrap.
+ *
+ * Possible reasons for the returned errno values:
+ *
+ * -ENXIO	if PLPKS is not supported
+ * -EIO		if PLPKS access is blocked due to the LPAR's state
+ *		if PLPKS modification is blocked due to the LPAR's state
+ *		if an error occurred while processing the request
+ * -EINVAL	if invalid authorization parameter
+ *		if invalid wrapping key label parameter
+ *		if invalid wrapping key label length parameter
+ *		if invalid or unsupported object wrapping flags
+ *		if invalid input buffer parameter
+ *		if invalid input buffer length parameter
+ *		if invalid output buffer parameter
+ *		if invalid output buffer length parameter
+ *		if invalid continue token parameter
+ *		if the wrapping key is not compatible with the wrapping
+ *		algorithm
+ * -EPERM	if access is denied
+ * -ENOENT	if the requested wrapping key was not found
+ * -EBUSY	if unable to handle the request or long running operation
+ *		initiated, retry later.
+ *
+ * Returns: On success 0 is returned, a negative errno if not.
+ */
+int plpks_wrap_object(u8 **input_buf, u32 input_len, u16 wrap_flags,
+		      u8 **output_buf, u32 *output_len)
+{
+	unsigned long retbuf[PLPAR_HCALL9_BUFSIZE] = { 0 };
+	struct plpks_auth *auth;
+	struct label *label;
+	u64 continuetoken = 0;
+	u64 objwrapflags = 0;
+	int rc = 0, pseries_status = 0;
+	bool sb_audit_or_enforce_bit = wrap_flags & BIT(0);
+	bool sb_enforce_bit = wrap_flags & BIT(1);
+	struct plpks_var var = {
+		.name = PLPKS_WRAPKEY_NAME,
+		.namelen = strlen(var.name),
+		.os = PLPKS_VAR_LINUX,
+		.component = PLPKS_WRAPKEY_COMPONENT
+	};
+
+	auth = construct_auth(PLPKS_OS_OWNER);
+	if (IS_ERR(auth))
+		return PTR_ERR(auth);
+
+	label = construct_label(var.component, var.os, var.name, var.namelen);
+	if (IS_ERR(label)) {
+		rc = PTR_ERR(label);
+		goto out;
+	}
+
+	/* Set the consumer password requirement bit. A must have. */
+	objwrapflags |= WRAPFLAG_BE_BIT_SET(3);
+
+	/* Set the wrapping algorithm bit. Just one algorithm option for now */
+	objwrapflags |= WRAPFLAG_BE_FIELD_PREP(60, 63, 0x1);
+
+	if (sb_audit_or_enforce_bit & sb_enforce_bit) {
+		pr_err("Cannot set both audit/enforce and enforce bits.");
+		rc = -EINVAL;
+		goto out_free_label;
+	} else if (sb_audit_or_enforce_bit) {
+		objwrapflags |= WRAPFLAG_BE_BIT_SET(1);
+	} else if (sb_enforce_bit) {
+		objwrapflags |= WRAPFLAG_BE_BIT_SET(2);
+	}
+
+	*output_len = input_len + PLPKS_WRAPPING_BUF_DIFF;
+
+	*output_buf = kzalloc(ALIGN(*output_len, PLPKS_WRAPPING_BUF_ALIGN),
+			      GFP_KERNEL);
+	if (!(*output_buf)) {
+		pr_err("Output buffer allocation failed. Returning -ENOMEM.");
+		rc = -ENOMEM;
+		goto out_free_label;
+	}
+
+	do {
+		rc = plpar_hcall9(H_PKS_WRAP_OBJECT, retbuf,
+				  virt_to_phys(auth), virt_to_phys(label),
+				  label->size, objwrapflags,
+				  virt_to_phys(*input_buf), input_len,
+				  virt_to_phys(*output_buf), *output_len,
+				  continuetoken);
+
+		continuetoken = retbuf[0];
+		pseries_status = rc;
+		rc = pseries_status_to_err(rc);
+	} while (rc == -EBUSY);
+
+	if (rc) {
+		pr_err("H_PKS_WRAP_OBJECT failed. pseries_status=%d, rc=%d",
+		       pseries_status, rc);
+		kfree(*output_buf);
+		*output_buf = NULL;
+	} else {
+		*output_len = retbuf[1];
+	}
+
+out_free_label:
+	kfree(label);
+out:
+	kfree(auth);
+	return rc;
+}
+EXPORT_SYMBOL_GPL(plpks_wrap_object);
+
+/**
+ * plpks_unwrap_object() - Unwrap an object using the default wrapping key
+ * stored in the PLPKS.
+ * @input_buf: buffer containing the data to be unwrapped
+ * @input_len: length of the input buffer
+ * @output_buf: buffer to store the unwrapped data
+ * @output_len: length of the output buffer
+ *
+ * The H_PKS_UNWRAP_OBJECT HCALL unwraps an object that was previously wrapped
+ * using the H_PKS_WRAP_OBJECT HCALL.
+ *
+ * Possible reasons for the returned errno values:
+ *
+ * -ENXIO	if PLPKS is not supported
+ * -EIO		if PLPKS access is blocked due to the LPAR's state
+ *		if PLPKS modification is blocked due to the LPAR's state
+ *		if an error occurred while processing the request
+ * -EINVAL	if invalid authorization parameter
+ *		if invalid or unsupported object unwrapping flags
+ *		if invalid input buffer parameter
+ *		if invalid input buffer length parameter
+ *		if invalid output buffer parameter
+ *		if invalid output buffer length parameter
+ *		if invalid continue token parameter
+ *		if the wrapping key is not compatible with the wrapping
+ *		algorithm
+ *		if the wrapped object's format is not supported
+ *		if the wrapped object is invalid
+ * -EPERM	if access is denied
+ * -ENOENT	if the wrapping key for the provided object was not found
+ * -EBUSY	if unable to handle the request or long running operation
+ *		initiated, retry later.
+ *
+ * Returns: On success 0 is returned, a negative errno if not.
+ */
+int plpks_unwrap_object(u8 **input_buf, u32 input_len, u8 **output_buf,
+			u32 *output_len)
+{
+	unsigned long retbuf[PLPAR_HCALL9_BUFSIZE] = { 0 };
+	struct plpks_auth *auth;
+	u64 continuetoken = 0;
+	u64 objwrapflags = 0;
+	int rc = 0, pseries_status = 0;
+
+	auth = construct_auth(PLPKS_OS_OWNER);
+	if (IS_ERR(auth))
+		return PTR_ERR(auth);
+
+	*output_len = input_len - PLPKS_WRAPPING_BUF_DIFF;
+	*output_buf = kzalloc(ALIGN(*output_len, PLPKS_WRAPPING_BUF_ALIGN),
+			      GFP_KERNEL);
+	if (!(*output_buf)) {
+		pr_err("Output buffer allocation failed. Returning -ENOMEM.");
+		rc = -ENOMEM;
+		goto out;
+	}
+
+	do {
+		rc = plpar_hcall9(H_PKS_UNWRAP_OBJECT, retbuf,
+				  virt_to_phys(auth), objwrapflags,
+				  virt_to_phys(*input_buf), input_len,
+				  virt_to_phys(*output_buf), *output_len,
+				  continuetoken);
+
+		continuetoken = retbuf[0];
+		pseries_status = rc;
+		rc = pseries_status_to_err(rc);
+	} while (rc == -EBUSY);
+
+	if (rc) {
+		pr_err("H_PKS_UNWRAP_OBJECT failed. pseries_status=%d, rc=%d",
+		       pseries_status, rc);
+		kfree(*output_buf);
+		*output_buf = NULL;
+	} else {
+		*output_len = retbuf[1];
+	}
+
+out:
+	kfree(auth);
+	return rc;
+}
+EXPORT_SYMBOL_GPL(plpks_unwrap_object);
+
+/**
+ * plpks_read_os_var() - Fetch the data for the specified variable that is owned
+ * by the OS consumer.
  * @var: variable to be read from the PLPKS
  *
  * The consumer or the owner of the object is the os kernel. The
-- 
2.47.3


^ permalink raw reply related

* [PATCH v6 6/6] docs: trusted-encryped: add PKWM as a new trust source
From: Srish Srinivasan @ 2026-02-01 13:59 UTC (permalink / raw)
  To: linux-integrity, keyrings, linuxppc-dev
  Cc: maddy, mpe, npiggin, christophe.leroy, James.Bottomley, jarkko,
	zohar, nayna, rnsastry, linux-kernel, linux-security-module,
	ssrish
In-Reply-To: <20260201135930.898721-1-ssrish@linux.ibm.com>

From: Nayna Jain <nayna@linux.ibm.com>

Update Documentation/security/keys/trusted-encrypted.rst and Documentation/
admin-guide/kernel-parameters.txt with PowerVM Key Wrapping Module (PKWM)
as a new trust source

Signed-off-by: Nayna Jain <nayna@linux.ibm.com>
Signed-off-by: Srish Srinivasan <ssrish@linux.ibm.com>
Reviewed-by: Mimi Zohar <zohar@linux.ibm.com>
---
 .../admin-guide/kernel-parameters.txt         |  1 +
 .../security/keys/trusted-encrypted.rst       | 50 +++++++++++++++++++
 2 files changed, 51 insertions(+)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 1058f2a6d6a8..aac15079b33d 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -7790,6 +7790,7 @@ Kernel parameters
 			- "tee"
 			- "caam"
 			- "dcp"
+			- "pkwm"
 			If not specified then it defaults to iterating through
 			the trust source list starting with TPM and assigns the
 			first trust source as a backend which is initialized
diff --git a/Documentation/security/keys/trusted-encrypted.rst b/Documentation/security/keys/trusted-encrypted.rst
index eae6a36b1c9a..ddff7c7c2582 100644
--- a/Documentation/security/keys/trusted-encrypted.rst
+++ b/Documentation/security/keys/trusted-encrypted.rst
@@ -81,6 +81,14 @@ safe.
          and the UNIQUE key. Default is to use the UNIQUE key, but selecting
          the OTP key can be done via a module parameter (dcp_use_otp_key).
 
+     (5) PKWM (PowerVM Key Wrapping Module: IBM PowerVM + Platform KeyStore)
+
+         Rooted to a unique, per-LPAR key, which is derived from a system-wide,
+         randomly generated LPAR root key. Both the per-LPAR keys and the LPAR
+         root key are stored in hypervisor-owned secure memory at runtime,
+         and the LPAR root key is additionally persisted in secure locations
+         such as the processor SEEPROMs and encrypted NVRAM.
+
   *  Execution isolation
 
      (1) TPM
@@ -102,6 +110,14 @@ safe.
          environment. Only basic blob key encryption is executed there.
          The actual key sealing/unsealing is done on main processor/kernel space.
 
+     (5) PKWM (PowerVM Key Wrapping Module: IBM PowerVM + Platform KeyStore)
+
+         Fixed set of cryptographic operations done on on-chip hardware
+         cryptographic acceleration unit NX. Keys for wrapping and unwrapping
+         are managed by PowerVM Platform KeyStore, which stores keys in an
+         isolated in-memory copy in secure hypervisor memory, as well as in a
+         persistent copy in hypervisor-encrypted NVRAM.
+
   * Optional binding to platform integrity state
 
      (1) TPM
@@ -129,6 +145,11 @@ safe.
          Relies on Secure/Trusted boot process (called HAB by vendor) for
          platform integrity.
 
+     (5) PKWM (PowerVM Key Wrapping Module: IBM PowerVM + Platform KeyStore)
+
+         Relies on secure and trusted boot process of IBM Power systems for
+         platform integrity.
+
   *  Interfaces and APIs
 
      (1) TPM
@@ -149,6 +170,11 @@ safe.
          Vendor-specific API that is implemented as part of the DCP crypto driver in
          ``drivers/crypto/mxs-dcp.c``.
 
+     (5) PKWM (PowerVM Key Wrapping Module: IBM PowerVM + Platform KeyStore)
+
+         Platform Keystore has well documented interfaces in PAPR document.
+         Refer to ``Documentation/arch/powerpc/papr_hcalls.rst``
+
   *  Threat model
 
      The strength and appropriateness of a particular trust source for a given
@@ -191,6 +217,10 @@ selected trust source:
      a dedicated hardware RNG that is independent from DCP which can be enabled
      to back the kernel RNG.
 
+   * PKWM (PowerVM Key Wrapping Module: IBM PowerVM + Platform KeyStore)
+
+     The normal kernel random number generator is used to generate keys.
+
 Users may override this by specifying ``trusted.rng=kernel`` on the kernel
 command-line to override the used RNG with the kernel's random number pool.
 
@@ -321,6 +351,26 @@ Usage::
 specific to this DCP key-blob implementation.  The key length for new keys is
 always in bytes. Trusted Keys can be 32 - 128 bytes (256 - 1024 bits).
 
+Trusted Keys usage: PKWM
+------------------------
+
+Usage::
+
+    keyctl add trusted name "new keylen [options]" ring
+    keyctl add trusted name "load hex_blob" ring
+    keyctl print keyid
+
+    options:
+       wrap_flags=   ascii hex value of security policy requirement
+                       0x00: no secure boot requirement (default)
+                       0x01: require secure boot to be in either audit or
+                             enforced mode
+                       0x02: require secure boot to be in enforced mode
+
+"keyctl print" returns an ASCII hex copy of the sealed key, which is in format
+specific to PKWM key-blob implementation.  The key length for new keys is
+always in bytes. Trusted Keys can be 32 - 128 bytes (256 - 1024 bits).
+
 Encrypted Keys usage
 --------------------
 
-- 
2.47.3


^ permalink raw reply related

* [PATCH v6 2/6] powerpc/pseries: move the PLPKS config inside its own sysfs directory
From: Srish Srinivasan @ 2026-02-01 13:59 UTC (permalink / raw)
  To: linux-integrity, keyrings, linuxppc-dev
  Cc: maddy, mpe, npiggin, christophe.leroy, James.Bottomley, jarkko,
	zohar, nayna, rnsastry, linux-kernel, linux-security-module,
	ssrish
In-Reply-To: <20260201135930.898721-1-ssrish@linux.ibm.com>

The /sys/firmware/secvar/config directory represents Power LPAR Platform
KeyStore (PLPKS) configuration properties such as max_object_size, signed_
update_algorithms, supported_policies, total_size, used_space, and version.
These attributes describe the PLPKS, and not the secure boot variables
(secvars).

Create /sys/firmware/plpks directory and move the PLPKS config inside this
directory. For backwards compatibility, create a soft link from the secvar
sysfs directory to this config and emit a warning stating that the older
sysfs path has been deprecated. Separate out the plpks specific
documentation from secvar.

Signed-off-by: Srish Srinivasan <ssrish@linux.ibm.com>
Reviewed-by: Mimi Zohar <zohar@linux.ibm.com>
Reviewed-by: Nayna Jain <nayna@linux.ibm.com>
Tested-by: Nayna Jain <nayna@linux.ibm.com>
---
 .../ABI/testing/sysfs-firmware-plpks          | 50 ++++++++++
 Documentation/ABI/testing/sysfs-secvar        | 65 -------------
 arch/powerpc/include/asm/plpks.h              |  5 +
 arch/powerpc/include/asm/secvar.h             |  1 -
 arch/powerpc/kernel/secvar-sysfs.c            | 21 ++---
 arch/powerpc/platforms/pseries/Makefile       |  2 +-
 arch/powerpc/platforms/pseries/plpks-secvar.c | 29 ------
 arch/powerpc/platforms/pseries/plpks-sysfs.c  | 94 +++++++++++++++++++
 8 files changed, 156 insertions(+), 111 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-firmware-plpks
 create mode 100644 arch/powerpc/platforms/pseries/plpks-sysfs.c

diff --git a/Documentation/ABI/testing/sysfs-firmware-plpks b/Documentation/ABI/testing/sysfs-firmware-plpks
new file mode 100644
index 000000000000..af0353f34115
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-firmware-plpks
@@ -0,0 +1,50 @@
+What:		/sys/firmware/plpks/config
+Date:		February 2023
+Contact:	Nayna Jain <nayna@linux.ibm.com>
+Description:	This optional directory contains read-only config attributes as
+		defined by the PLPKS implementation. All data is in ASCII
+		format.
+
+What:		/sys/firmware/plpks/config/version
+Date:		February 2023
+Contact:	Nayna Jain <nayna@linux.ibm.com>
+Description:	Config version as reported by the hypervisor in ASCII decimal
+		format.
+
+What:		/sys/firmware/plpks/config/max_object_size
+Date:		February 2023
+Contact:	Nayna Jain <nayna@linux.ibm.com>
+Description:	Maximum allowed size of	objects in the keystore in bytes,
+		represented in ASCII decimal format.
+
+		This is not necessarily the same as the max size that can be
+		written to an update file as writes can contain more than
+		object data, you should use the size of the update file for
+		that purpose.
+
+What:		/sys/firmware/plpks/config/total_size
+Date:		February 2023
+Contact:	Nayna Jain <nayna@linux.ibm.com>
+Description:	Total size of the PLPKS in bytes, represented in ASCII decimal
+		format.
+
+What:		/sys/firmware/plpks/config/used_space
+Date:		February 2023
+Contact:	Nayna Jain <nayna@linux.ibm.com>
+Description:	Current space consumed by the key store, in bytes, represented
+		in ASCII decimal format.
+
+What:		/sys/firmware/plpks/config/supported_policies
+Date:		February 2023
+Contact:	Nayna Jain <nayna@linux.ibm.com>
+Description:	Bitmask of supported policy flags by the hypervisor, represented
+		as an 8 byte hexadecimal ASCII string. Consult the hypervisor
+		documentation for what these flags are.
+
+What:		/sys/firmware/plpks/config/signed_update_algorithms
+Date:		February 2023
+Contact:	Nayna Jain <nayna@linux.ibm.com>
+Description:	Bitmask of flags indicating which algorithms the hypervisor
+		supports for signed update of objects, represented as a 16 byte
+		hexadecimal ASCII string. Consult the hypervisor documentation
+		for what these flags mean.
diff --git a/Documentation/ABI/testing/sysfs-secvar b/Documentation/ABI/testing/sysfs-secvar
index 1016967a730f..c52a5fd15709 100644
--- a/Documentation/ABI/testing/sysfs-secvar
+++ b/Documentation/ABI/testing/sysfs-secvar
@@ -63,68 +63,3 @@ Contact:	Nayna Jain <nayna@linux.ibm.com>
 Description:	A write-only file that is used to submit the new value for the
 		variable. The size of the file represents the maximum size of
 		the variable data that can be written.
-
-What:		/sys/firmware/secvar/config
-Date:		February 2023
-Contact:	Nayna Jain <nayna@linux.ibm.com>
-Description:	This optional directory contains read-only config attributes as
-		defined by the secure variable implementation.  All data is in
-		ASCII format. The directory is only created if the backing
-		implementation provides variables to populate it, which at
-		present is only PLPKS on the pseries platform.
-
-What:		/sys/firmware/secvar/config/version
-Date:		February 2023
-Contact:	Nayna Jain <nayna@linux.ibm.com>
-Description:	Config version as reported by the hypervisor in ASCII decimal
-		format.
-
-		Currently only provided by PLPKS on the pseries platform.
-
-What:		/sys/firmware/secvar/config/max_object_size
-Date:		February 2023
-Contact:	Nayna Jain <nayna@linux.ibm.com>
-Description:	Maximum allowed size of	objects in the keystore in bytes,
-		represented in ASCII decimal format.
-
-		This is not necessarily the same as the max size that can be
-		written to an update file as writes can contain more than
-		object data, you should use the size of the update file for
-		that purpose.
-
-		Currently only provided by PLPKS on the pseries platform.
-
-What:		/sys/firmware/secvar/config/total_size
-Date:		February 2023
-Contact:	Nayna Jain <nayna@linux.ibm.com>
-Description:	Total size of the PLPKS in bytes, represented in ASCII decimal
-		format.
-
-		Currently only provided by PLPKS on the pseries platform.
-
-What:		/sys/firmware/secvar/config/used_space
-Date:		February 2023
-Contact:	Nayna Jain <nayna@linux.ibm.com>
-Description:	Current space consumed by the key store, in bytes, represented
-		in ASCII decimal format.
-
-		Currently only provided by PLPKS on the pseries platform.
-
-What:		/sys/firmware/secvar/config/supported_policies
-Date:		February 2023
-Contact:	Nayna Jain <nayna@linux.ibm.com>
-Description:	Bitmask of supported policy flags by the hypervisor,
-		represented as an 8 byte hexadecimal ASCII string. Consult the
-		hypervisor documentation for what these flags are.
-
-		Currently only provided by PLPKS on the pseries platform.
-
-What:		/sys/firmware/secvar/config/signed_update_algorithms
-Date:		February 2023
-Contact:	Nayna Jain <nayna@linux.ibm.com>
-Description:	Bitmask of flags indicating which algorithms the hypervisor
-		supports for signed update of objects, represented as a 16 byte
-		hexadecimal ASCII string. Consult the hypervisor documentation
-		for what these flags mean.
-
-		Currently only provided by PLPKS on the pseries platform.
diff --git a/arch/powerpc/include/asm/plpks.h b/arch/powerpc/include/asm/plpks.h
index f303922bf622..8829a13bfda0 100644
--- a/arch/powerpc/include/asm/plpks.h
+++ b/arch/powerpc/include/asm/plpks.h
@@ -13,6 +13,7 @@
 
 #include <linux/types.h>
 #include <linux/list.h>
+#include <linux/kobject.h>
 
 // Object policy flags from supported_policies
 #define PLPKS_OSSECBOOTAUDIT	PPC_BIT32(1) // OS secure boot must be audit/enforce
@@ -107,11 +108,15 @@ u16 plpks_get_passwordlen(void);
 void plpks_early_init_devtree(void);
 
 int plpks_populate_fdt(void *fdt);
+
+int plpks_config_create_softlink(struct kobject *from);
 #else // CONFIG_PSERIES_PLPKS
 static inline bool plpks_is_available(void) { return false; }
 static inline u16 plpks_get_passwordlen(void) { BUILD_BUG(); }
 static inline void plpks_early_init_devtree(void) { }
 static inline int plpks_populate_fdt(void *fdt) { BUILD_BUG(); }
+static inline int plpks_config_create_softlink(struct kobject *from)
+						{ return 0; }
 #endif // CONFIG_PSERIES_PLPKS
 
 #endif // _ASM_POWERPC_PLPKS_H
diff --git a/arch/powerpc/include/asm/secvar.h b/arch/powerpc/include/asm/secvar.h
index 4828e0ab7e3c..fd5006307f2a 100644
--- a/arch/powerpc/include/asm/secvar.h
+++ b/arch/powerpc/include/asm/secvar.h
@@ -20,7 +20,6 @@ struct secvar_operations {
 	int (*set)(const char *key, u64 key_len, u8 *data, u64 data_size);
 	ssize_t (*format)(char *buf, size_t bufsize);
 	int (*max_size)(u64 *max_size);
-	const struct attribute **config_attrs;
 
 	// NULL-terminated array of fixed variable names
 	// Only used if get_next() isn't provided
diff --git a/arch/powerpc/kernel/secvar-sysfs.c b/arch/powerpc/kernel/secvar-sysfs.c
index ec900bce0257..4111b21962eb 100644
--- a/arch/powerpc/kernel/secvar-sysfs.c
+++ b/arch/powerpc/kernel/secvar-sysfs.c
@@ -12,6 +12,7 @@
 #include <linux/string.h>
 #include <linux/of.h>
 #include <asm/secvar.h>
+#include <asm/plpks.h>
 
 #define NAME_MAX_SIZE	   1024
 
@@ -145,19 +146,6 @@ static __init int update_kobj_size(void)
 	return 0;
 }
 
-static __init int secvar_sysfs_config(struct kobject *kobj)
-{
-	struct attribute_group config_group = {
-		.name = "config",
-		.attrs = (struct attribute **)secvar_ops->config_attrs,
-	};
-
-	if (secvar_ops->config_attrs)
-		return sysfs_create_group(kobj, &config_group);
-
-	return 0;
-}
-
 static __init int add_var(const char *name)
 {
 	struct kobject *kobj;
@@ -260,12 +248,15 @@ static __init int secvar_sysfs_init(void)
 		goto err;
 	}
 
-	rc = secvar_sysfs_config(secvar_kobj);
+	rc = plpks_config_create_softlink(secvar_kobj);
 	if (rc) {
-		pr_err("Failed to create config directory\n");
+		pr_err("Failed to create softlink to PLPKS config directory");
 		goto err;
 	}
 
+	pr_info("/sys/firmware/secvar/config is now deprecated.\n");
+	pr_info("Will be removed in future versions.\n");
+
 	if (secvar_ops->get_next)
 		rc = secvar_sysfs_load();
 	else
diff --git a/arch/powerpc/platforms/pseries/Makefile b/arch/powerpc/platforms/pseries/Makefile
index 931ebaa474c8..3ced289a675b 100644
--- a/arch/powerpc/platforms/pseries/Makefile
+++ b/arch/powerpc/platforms/pseries/Makefile
@@ -30,7 +30,7 @@ obj-$(CONFIG_PAPR_SCM)		+= papr_scm.o
 obj-$(CONFIG_PPC_SPLPAR)	+= vphn.o
 obj-$(CONFIG_PPC_SVM)		+= svm.o
 obj-$(CONFIG_FA_DUMP)		+= rtas-fadump.o
-obj-$(CONFIG_PSERIES_PLPKS)	+= plpks.o
+obj-$(CONFIG_PSERIES_PLPKS)	+= plpks.o plpks-sysfs.o
 obj-$(CONFIG_PPC_SECURE_BOOT)	+= plpks-secvar.o
 obj-$(CONFIG_PSERIES_PLPKS_SED)	+= plpks_sed_ops.o
 obj-$(CONFIG_SUSPEND)		+= suspend.o
diff --git a/arch/powerpc/platforms/pseries/plpks-secvar.c b/arch/powerpc/platforms/pseries/plpks-secvar.c
index f9e9cc40c9d0..a50ff6943d80 100644
--- a/arch/powerpc/platforms/pseries/plpks-secvar.c
+++ b/arch/powerpc/platforms/pseries/plpks-secvar.c
@@ -20,33 +20,6 @@
 #include <asm/secvar.h>
 #include <asm/plpks.h>
 
-// Config attributes for sysfs
-#define PLPKS_CONFIG_ATTR(name, fmt, func)			\
-	static ssize_t name##_show(struct kobject *kobj,	\
-				   struct kobj_attribute *attr,	\
-				   char *buf)			\
-	{							\
-		return sysfs_emit(buf, fmt, func());		\
-	}							\
-	static struct kobj_attribute attr_##name = __ATTR_RO(name)
-
-PLPKS_CONFIG_ATTR(version, "%u\n", plpks_get_version);
-PLPKS_CONFIG_ATTR(max_object_size, "%u\n", plpks_get_maxobjectsize);
-PLPKS_CONFIG_ATTR(total_size, "%u\n", plpks_get_totalsize);
-PLPKS_CONFIG_ATTR(used_space, "%u\n", plpks_get_usedspace);
-PLPKS_CONFIG_ATTR(supported_policies, "%08x\n", plpks_get_supportedpolicies);
-PLPKS_CONFIG_ATTR(signed_update_algorithms, "%016llx\n", plpks_get_signedupdatealgorithms);
-
-static const struct attribute *config_attrs[] = {
-	&attr_version.attr,
-	&attr_max_object_size.attr,
-	&attr_total_size.attr,
-	&attr_used_space.attr,
-	&attr_supported_policies.attr,
-	&attr_signed_update_algorithms.attr,
-	NULL,
-};
-
 static u32 get_policy(const char *name)
 {
 	if ((strcmp(name, "db") == 0) ||
@@ -225,7 +198,6 @@ static const struct secvar_operations plpks_secvar_ops_static = {
 	.set = plpks_set_variable,
 	.format = plpks_secvar_format,
 	.max_size = plpks_max_size,
-	.config_attrs = config_attrs,
 	.var_names = plpks_var_names_static,
 };
 
@@ -234,7 +206,6 @@ static const struct secvar_operations plpks_secvar_ops_dynamic = {
 	.set = plpks_set_variable,
 	.format = plpks_secvar_format,
 	.max_size = plpks_max_size,
-	.config_attrs = config_attrs,
 	.var_names = plpks_var_names_dynamic,
 };
 
diff --git a/arch/powerpc/platforms/pseries/plpks-sysfs.c b/arch/powerpc/platforms/pseries/plpks-sysfs.c
new file mode 100644
index 000000000000..01d526185783
--- /dev/null
+++ b/arch/powerpc/platforms/pseries/plpks-sysfs.c
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 IBM Corporation, Srish Srinivasan <ssrish@linux.ibm.com>
+ *
+ * This code exposes PLPKS config to user via sysfs
+ */
+
+#define pr_fmt(fmt) "plpks-sysfs: "fmt
+
+#include <linux/init.h>
+#include <linux/printk.h>
+#include <linux/types.h>
+#include <asm/machdep.h>
+#include <asm/plpks.h>
+
+/* config attributes for sysfs */
+#define PLPKS_CONFIG_ATTR(name, fmt, func)			\
+	static ssize_t name##_show(struct kobject *kobj,	\
+				   struct kobj_attribute *attr,	\
+				   char *buf)			\
+	{							\
+		return sysfs_emit(buf, fmt, func());		\
+	}							\
+	static struct kobj_attribute attr_##name = __ATTR_RO(name)
+
+PLPKS_CONFIG_ATTR(version, "%u\n", plpks_get_version);
+PLPKS_CONFIG_ATTR(max_object_size, "%u\n", plpks_get_maxobjectsize);
+PLPKS_CONFIG_ATTR(total_size, "%u\n", plpks_get_totalsize);
+PLPKS_CONFIG_ATTR(used_space, "%u\n", plpks_get_usedspace);
+PLPKS_CONFIG_ATTR(supported_policies, "%08x\n", plpks_get_supportedpolicies);
+PLPKS_CONFIG_ATTR(signed_update_algorithms, "%016llx\n",
+		  plpks_get_signedupdatealgorithms);
+
+static const struct attribute *config_attrs[] = {
+	&attr_version.attr,
+	&attr_max_object_size.attr,
+	&attr_total_size.attr,
+	&attr_used_space.attr,
+	&attr_supported_policies.attr,
+	&attr_signed_update_algorithms.attr,
+	NULL,
+};
+
+static struct kobject *plpks_kobj, *plpks_config_kobj;
+
+int plpks_config_create_softlink(struct kobject *from)
+{
+	if (!plpks_config_kobj)
+		return -EINVAL;
+	return sysfs_create_link(from, plpks_config_kobj, "config");
+}
+
+static __init int plpks_sysfs_config(struct kobject *kobj)
+{
+	struct attribute_group config_group = {
+		.name = NULL,
+		.attrs = (struct attribute **)config_attrs,
+	};
+
+	return sysfs_create_group(kobj, &config_group);
+}
+
+static __init int plpks_sysfs_init(void)
+{
+	int rc;
+
+	if (!plpks_is_available())
+		return -ENODEV;
+
+	plpks_kobj = kobject_create_and_add("plpks", firmware_kobj);
+	if (!plpks_kobj) {
+		pr_err("Failed to create plpks kobj\n");
+		return -ENOMEM;
+	}
+
+	plpks_config_kobj = kobject_create_and_add("config", plpks_kobj);
+	if (!plpks_config_kobj) {
+		pr_err("Failed to create plpks config kobj\n");
+		kobject_put(plpks_kobj);
+		return -ENOMEM;
+	}
+
+	rc = plpks_sysfs_config(plpks_config_kobj);
+	if (rc) {
+		pr_err("Failed to create attribute group for plpks config\n");
+		kobject_put(plpks_config_kobj);
+		kobject_put(plpks_kobj);
+		return rc;
+	}
+
+	return 0;
+}
+
+machine_subsys_initcall(pseries, plpks_sysfs_init);
-- 
2.47.3


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox