Linux Documentation
 help / color / mirror / Atom feed
* [PATCH v6 02/43] KVM: Rename KVM_GENERIC_MEMORY_ATTRIBUTES to KVM_VM_MEMORY_ATTRIBUTES
From: Ackerley Tng via B4 Relay @ 2026-05-07 20:22 UTC (permalink / raw)
  To: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
	ira.weiny, jmattson, jthoughton, michael.roth, oupton,
	pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
	steven.price, tabba, willy, wyihan, yan.y.zhao, forkloop,
	pratyush, suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
	Sean Christopherson, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, Steven Rostedt,
	Masami Hiramatsu, Mathieu Desnoyers, Jonathan Corbet, Shuah Khan,
	Shuah Khan, Vishal Annapurve, Andrew Morton, Chris Li,
	Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He, Barry Song,
	Axel Rasmussen, Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng,
	Shakeel Butt, Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka
  Cc: kvm, linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
	linux-mm, linux-coco, Ackerley Tng
In-Reply-To: <20260507-gmem-inplace-conversion-v6-0-91ab5a8b19a4@google.com>

From: Sean Christopherson <seanjc@google.com>

Rename the per-VM memory attributes Kconfig to make it explicitly about
per-VM attributes in anticipation of adding memory attributes support to
guest_memfd, at which point it will be possible (and desirable) to have
memory attributes without the per-VM support, even in x86.

No functional change intended.

Signed-off-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
 arch/x86/include/asm/kvm_host.h |  2 +-
 arch/x86/kvm/Kconfig            |  6 +++---
 arch/x86/kvm/mmu/mmu.c          |  2 +-
 arch/x86/kvm/x86.c              |  2 +-
 include/linux/kvm_host.h        |  8 ++++----
 include/trace/events/kvm.h      |  4 ++--
 virt/kvm/Kconfig                |  2 +-
 virt/kvm/kvm_main.c             | 14 +++++++-------
 8 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index c470e40a00aa4..60b997764beef 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -2369,7 +2369,7 @@ void kvm_configure_mmu(bool enable_tdp, int tdp_forced_root_level,
 		       int tdp_max_root_level, int tdp_huge_page_level);
 
 
-#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES
+#ifdef CONFIG_KVM_VM_MEMORY_ATTRIBUTES
 #define kvm_arch_has_private_mem(kvm) ((kvm)->arch.has_private_mem)
 #endif
 
diff --git a/arch/x86/kvm/Kconfig b/arch/x86/kvm/Kconfig
index 801bf9e520db3..26f6afd51bbdc 100644
--- a/arch/x86/kvm/Kconfig
+++ b/arch/x86/kvm/Kconfig
@@ -84,7 +84,7 @@ config KVM_SW_PROTECTED_VM
 	bool "Enable support for KVM software-protected VMs"
 	depends on EXPERT
 	depends on KVM_X86 && X86_64
-	select KVM_GENERIC_MEMORY_ATTRIBUTES
+	select KVM_VM_MEMORY_ATTRIBUTES
 	help
 	  Enable support for KVM software-protected VMs.  Currently, software-
 	  protected VMs are purely a development and testing vehicle for
@@ -135,7 +135,7 @@ config KVM_INTEL_TDX
 	bool "Intel Trust Domain Extensions (TDX) support"
 	default y
 	depends on INTEL_TDX_HOST
-	select KVM_GENERIC_MEMORY_ATTRIBUTES
+	select KVM_VM_MEMORY_ATTRIBUTES
 	select HAVE_KVM_ARCH_GMEM_POPULATE
 	help
 	  Provides support for launching Intel Trust Domain Extensions (TDX)
@@ -159,7 +159,7 @@ config KVM_AMD_SEV
 	depends on KVM_AMD && X86_64
 	depends on CRYPTO_DEV_SP_PSP && !(KVM_AMD=y && CRYPTO_DEV_CCP_DD=m)
 	select ARCH_HAS_CC_PLATFORM
-	select KVM_GENERIC_MEMORY_ATTRIBUTES
+	select KVM_VM_MEMORY_ATTRIBUTES
 	select HAVE_KVM_ARCH_GMEM_PREPARE
 	select HAVE_KVM_ARCH_GMEM_INVALIDATE
 	select HAVE_KVM_ARCH_GMEM_POPULATE
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 892246204435c..a80a876ab4ad6 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -7899,7 +7899,7 @@ void kvm_mmu_pre_destroy_vm(struct kvm *kvm)
 		vhost_task_stop(kvm->arch.nx_huge_page_recovery_thread);
 }
 
-#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES
+#ifdef CONFIG_KVM_VM_MEMORY_ATTRIBUTES
 static bool hugepage_test_mixed(struct kvm_memory_slot *slot, gfn_t gfn,
 				int level)
 {
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 0a1b63c63d1a9..1560de1e95be0 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -13625,7 +13625,7 @@ static int kvm_alloc_memslot_metadata(struct kvm *kvm,
 		}
 	}
 
-#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES
+#ifdef CONFIG_KVM_VM_MEMORY_ATTRIBUTES
 	kvm_mmu_init_memslot_memory_attributes(kvm, slot);
 #endif
 
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index 4c14aee1fb063..7b9faa3545300 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -722,7 +722,7 @@ static inline int kvm_arch_vcpu_memslots_id(struct kvm_vcpu *vcpu)
 }
 #endif
 
-#ifndef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES
+#ifndef CONFIG_KVM_VM_MEMORY_ATTRIBUTES
 static inline bool kvm_arch_has_private_mem(struct kvm *kvm)
 {
 	return false;
@@ -871,7 +871,7 @@ struct kvm {
 #ifdef CONFIG_HAVE_KVM_PM_NOTIFIER
 	struct notifier_block pm_notifier;
 #endif
-#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES
+#ifdef CONFIG_KVM_VM_MEMORY_ATTRIBUTES
 	/* Protected by slots_lock (for writes) and RCU (for reads) */
 	struct xarray mem_attr_array;
 #endif
@@ -2528,7 +2528,7 @@ static inline bool kvm_memslot_is_gmem_only(const struct kvm_memory_slot *slot)
 	return slot->flags & KVM_MEMSLOT_GMEM_ONLY;
 }
 
-#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES
+#ifdef CONFIG_KVM_VM_MEMORY_ATTRIBUTES
 static inline unsigned long kvm_get_memory_attributes(struct kvm *kvm, gfn_t gfn)
 {
 	return xa_to_value(xa_load(&kvm->mem_attr_array, gfn));
@@ -2550,7 +2550,7 @@ static inline bool kvm_mem_is_private(struct kvm *kvm, gfn_t gfn)
 {
 	return false;
 }
-#endif /* CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES */
+#endif /* CONFIG_KVM_VM_MEMORY_ATTRIBUTES */
 
 #ifdef CONFIG_KVM_GUEST_MEMFD
 int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,
diff --git a/include/trace/events/kvm.h b/include/trace/events/kvm.h
index b282e3a867696..1ba72bd73ea2f 100644
--- a/include/trace/events/kvm.h
+++ b/include/trace/events/kvm.h
@@ -358,7 +358,7 @@ TRACE_EVENT(kvm_dirty_ring_exit,
 	TP_printk("vcpu %d", __entry->vcpu_id)
 );
 
-#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES
+#ifdef CONFIG_KVM_VM_MEMORY_ATTRIBUTES
 /*
  * @start:	Starting address of guest memory range
  * @end:	End address of guest memory range
@@ -383,7 +383,7 @@ TRACE_EVENT(kvm_vm_set_mem_attributes,
 	TP_printk("%#016llx -- %#016llx [0x%lx]",
 		  __entry->start, __entry->end, __entry->attr)
 );
-#endif /* CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES */
+#endif /* CONFIG_KVM_VM_MEMORY_ATTRIBUTES */
 
 TRACE_EVENT(kvm_unmap_hva_range,
 	TP_PROTO(unsigned long start, unsigned long end),
diff --git a/virt/kvm/Kconfig b/virt/kvm/Kconfig
index 794976b88c6f9..5119cb37145fc 100644
--- a/virt/kvm/Kconfig
+++ b/virt/kvm/Kconfig
@@ -100,7 +100,7 @@ config KVM_ELIDE_TLB_FLUSH_IF_YOUNG
 config KVM_MMU_LOCKLESS_AGING
        bool
 
-config KVM_GENERIC_MEMORY_ATTRIBUTES
+config KVM_VM_MEMORY_ATTRIBUTES
        bool
 
 config KVM_GUEST_MEMFD
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 89489996fbc1e..306153abbafa5 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -1115,7 +1115,7 @@ static struct kvm *kvm_create_vm(unsigned long type, const char *fdname)
 	spin_lock_init(&kvm->mn_invalidate_lock);
 	rcuwait_init(&kvm->mn_memslots_update_rcuwait);
 	xa_init(&kvm->vcpu_array);
-#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES
+#ifdef CONFIG_KVM_VM_MEMORY_ATTRIBUTES
 	xa_init(&kvm->mem_attr_array);
 #endif
 
@@ -1300,7 +1300,7 @@ static void kvm_destroy_vm(struct kvm *kvm)
 	cleanup_srcu_struct(&kvm->irq_srcu);
 	srcu_barrier(&kvm->srcu);
 	cleanup_srcu_struct(&kvm->srcu);
-#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES
+#ifdef CONFIG_KVM_VM_MEMORY_ATTRIBUTES
 	xa_destroy(&kvm->mem_attr_array);
 #endif
 	kvm_arch_free_vm(kvm);
@@ -2418,7 +2418,7 @@ static int kvm_vm_ioctl_clear_dirty_log(struct kvm *kvm,
 }
 #endif /* CONFIG_KVM_GENERIC_DIRTYLOG_READ_PROTECT */
 
-#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES
+#ifdef CONFIG_KVM_VM_MEMORY_ATTRIBUTES
 static u64 kvm_supported_mem_attributes(struct kvm *kvm)
 {
 	if (!kvm || kvm_arch_has_private_mem(kvm))
@@ -2623,7 +2623,7 @@ static int kvm_vm_ioctl_set_mem_attributes(struct kvm *kvm,
 
 	return kvm_vm_set_mem_attributes(kvm, start, end, attrs->attributes);
 }
-#endif /* CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES */
+#endif /* CONFIG_KVM_VM_MEMORY_ATTRIBUTES */
 
 struct kvm_memory_slot *gfn_to_memslot(struct kvm *kvm, gfn_t gfn)
 {
@@ -4921,7 +4921,7 @@ static int kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg)
 	case KVM_CAP_SYSTEM_EVENT_DATA:
 	case KVM_CAP_DEVICE_CTRL:
 		return 1;
-#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES
+#ifdef CONFIG_KVM_VM_MEMORY_ATTRIBUTES
 	case KVM_CAP_MEMORY_ATTRIBUTES:
 		return kvm_supported_mem_attributes(kvm);
 #endif
@@ -5325,7 +5325,7 @@ static long kvm_vm_ioctl(struct file *filp,
 		break;
 	}
 #endif /* CONFIG_HAVE_KVM_IRQ_ROUTING */
-#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES
+#ifdef CONFIG_KVM_VM_MEMORY_ATTRIBUTES
 	case KVM_SET_MEMORY_ATTRIBUTES: {
 		struct kvm_memory_attributes attrs;
 
@@ -5336,7 +5336,7 @@ static long kvm_vm_ioctl(struct file *filp,
 		r = kvm_vm_ioctl_set_mem_attributes(kvm, &attrs);
 		break;
 	}
-#endif /* CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES */
+#endif /* CONFIG_KVM_VM_MEMORY_ATTRIBUTES */
 	case KVM_CREATE_DEVICE: {
 		struct kvm_create_device cd;
 

-- 
2.54.0.563.g4f69b47b94-goog



^ permalink raw reply related

* [PATCH v6 01/43] KVM: guest_memfd: Introduce per-gmem attributes, use to guard user mappings
From: Ackerley Tng via B4 Relay @ 2026-05-07 20:22 UTC (permalink / raw)
  To: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
	ira.weiny, jmattson, jthoughton, michael.roth, oupton,
	pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
	steven.price, tabba, willy, wyihan, yan.y.zhao, forkloop,
	pratyush, suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
	Sean Christopherson, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, Steven Rostedt,
	Masami Hiramatsu, Mathieu Desnoyers, Jonathan Corbet, Shuah Khan,
	Shuah Khan, Vishal Annapurve, Andrew Morton, Chris Li,
	Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He, Barry Song,
	Axel Rasmussen, Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng,
	Shakeel Butt, Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka
  Cc: kvm, linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
	linux-mm, linux-coco, Ackerley Tng
In-Reply-To: <20260507-gmem-inplace-conversion-v6-0-91ab5a8b19a4@google.com>

From: Sean Christopherson <seanjc@google.com>

Start plumbing in guest_memfd support for in-place private<=>shared
conversions by tracking attributes via a maple tree.  KVM currently tracks
private vs. shared attributes on a per-VM basis, which made sense when a
guest_memfd _only_ supported private memory, but tracking per-VM simply
can't work for in-place conversions as the shareability of a given page
needs to be per-gmem_inode, not per-VM.

Use the filemap invalidation lock to protect the maple tree, as taking the
lock for read when faulting in memory (for userspace or the guest) isn't
expected to result in meaningful contention, and using a separate lock
would add significant complexity (avoid deadlock is quite difficult).

Signed-off-by: Sean Christopherson <seanjc@google.com>
Co-developed-by: Ackerley Tng <ackerleytng@google.com>
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
Co-developed-by: Vishal Annapurve <vannapurve@google.com>
Signed-off-by: Vishal Annapurve <vannapurve@google.com>
Co-developed-by: Fuad Tabba <tabba@google.com>
Signed-off-by: Fuad Tabba <tabba@google.com>
---
 virt/kvm/guest_memfd.c | 133 +++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 117 insertions(+), 16 deletions(-)

diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 69c9d6d546b28..5011d38820d0d 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -4,6 +4,7 @@
 #include <linux/falloc.h>
 #include <linux/fs.h>
 #include <linux/kvm_host.h>
+#include <linux/maple_tree.h>
 #include <linux/mempolicy.h>
 #include <linux/pseudo_fs.h>
 #include <linux/pagemap.h>
@@ -33,6 +34,13 @@ struct gmem_inode {
 	struct list_head gmem_file_list;
 
 	u64 flags;
+	/*
+	 * Every index in this inode, whether memory is populated or
+	 * not, is tracked in attributes. The entire range of indices,
+	 * corresponding to the size of this inode, is represented in
+	 * this maple tree.
+	 */
+	struct maple_tree attributes;
 };
 
 static __always_inline struct gmem_inode *GMEM_I(struct inode *inode)
@@ -60,6 +68,24 @@ static pgoff_t kvm_gmem_get_index(struct kvm_memory_slot *slot, gfn_t gfn)
 	return gfn - slot->base_gfn + slot->gmem.pgoff;
 }
 
+static u64 kvm_gmem_get_attributes(struct inode *inode, pgoff_t index)
+{
+	struct maple_tree *mt = &GMEM_I(inode)->attributes;
+	void *entry = mtree_load(mt, index);
+
+	return WARN_ON_ONCE(!entry) ? 0 : xa_to_value(entry);
+}
+
+static bool kvm_gmem_is_private_mem(struct inode *inode, pgoff_t index)
+{
+	return kvm_gmem_get_attributes(inode, index) & KVM_MEMORY_ATTRIBUTE_PRIVATE;
+}
+
+static bool kvm_gmem_is_shared_mem(struct inode *inode, pgoff_t index)
+{
+	return !kvm_gmem_is_private_mem(inode, index);
+}
+
 static int __kvm_gmem_prepare_folio(struct kvm *kvm, struct kvm_memory_slot *slot,
 				    pgoff_t index, struct folio *folio)
 {
@@ -397,10 +423,13 @@ static vm_fault_t kvm_gmem_fault_user_mapping(struct vm_fault *vmf)
 	if (((loff_t)vmf->pgoff << PAGE_SHIFT) >= i_size_read(inode))
 		return VM_FAULT_SIGBUS;
 
-	if (!(GMEM_I(inode)->flags & GUEST_MEMFD_FLAG_INIT_SHARED))
-		return VM_FAULT_SIGBUS;
+	filemap_invalidate_lock_shared(inode->i_mapping);
+	if (kvm_gmem_is_shared_mem(inode, vmf->pgoff))
+		folio = kvm_gmem_get_folio(inode, vmf->pgoff);
+	else
+		folio = ERR_PTR(-EACCES);
+	filemap_invalidate_unlock_shared(inode->i_mapping);
 
-	folio = kvm_gmem_get_folio(inode, vmf->pgoff);
 	if (IS_ERR(folio)) {
 		if (PTR_ERR(folio) == -EAGAIN)
 			return VM_FAULT_RETRY;
@@ -556,6 +585,51 @@ bool __weak kvm_arch_supports_gmem_init_shared(struct kvm *kvm)
 	return true;
 }
 
+static int kvm_gmem_init_inode(struct inode *inode, loff_t size, u64 flags)
+{
+	struct gmem_inode *gi = GMEM_I(inode);
+	MA_STATE(mas, &gi->attributes, 0, (size >> PAGE_SHIFT) - 1);
+	u64 attrs;
+	int r;
+
+	inode->i_op = &kvm_gmem_iops;
+	inode->i_mapping->a_ops = &kvm_gmem_aops;
+	inode->i_mode |= S_IFREG;
+	inode->i_size = size;
+	mapping_set_gfp_mask(inode->i_mapping, GFP_HIGHUSER);
+
+	/*
+	 * guest_memfd memory is neither migratable nor swappable: set
+	 * inaccessible to gate off both.
+	 */
+	mapping_set_inaccessible(inode->i_mapping);
+	WARN_ON_ONCE(!mapping_unevictable(inode->i_mapping));
+
+	gi->flags = flags;
+
+	mt_set_external_lock(&gi->attributes,
+			     &inode->i_mapping->invalidate_lock);
+
+	/*
+	 * Store default attributes for the entire gmem instance. Ensuring every
+	 * index is represented in the maple tree at all times simplifies the
+	 * conversion and merging logic.
+	 */
+	attrs = gi->flags & GUEST_MEMFD_FLAG_INIT_SHARED ? 0 : KVM_MEMORY_ATTRIBUTE_PRIVATE;
+
+	/*
+	 * Acquire the invalidation lock purely to make lockdep happy.  The
+	 * maple tree library expects all stores to be protected via the lock,
+	 * and the library can't know when the tree is reachable only by the
+	 * caller, as is the case here.
+	 */
+	filemap_invalidate_lock(inode->i_mapping);
+	r = mas_store_gfp(&mas, xa_mk_value(attrs), GFP_KERNEL);
+	filemap_invalidate_unlock(inode->i_mapping);
+
+	return r;
+}
+
 static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags)
 {
 	static const char *name = "[kvm-gmem]";
@@ -586,16 +660,9 @@ static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags)
 		goto err_fops;
 	}
 
-	inode->i_op = &kvm_gmem_iops;
-	inode->i_mapping->a_ops = &kvm_gmem_aops;
-	inode->i_mode |= S_IFREG;
-	inode->i_size = size;
-	mapping_set_gfp_mask(inode->i_mapping, GFP_HIGHUSER);
-	mapping_set_inaccessible(inode->i_mapping);
-	/* Unmovable mappings are supposed to be marked unevictable as well. */
-	WARN_ON_ONCE(!mapping_unevictable(inode->i_mapping));
-
-	GMEM_I(inode)->flags = flags;
+	err = kvm_gmem_init_inode(inode, size, flags);
+	if (err)
+		goto err_inode;
 
 	file = alloc_file_pseudo(inode, kvm_gmem_mnt, name, O_RDWR, &kvm_gmem_fops);
 	if (IS_ERR(file)) {
@@ -797,9 +864,13 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,
 	if (!file)
 		return -EFAULT;
 
+	filemap_invalidate_lock_shared(file_inode(file)->i_mapping);
+
 	folio = __kvm_gmem_get_pfn(file, slot, index, pfn, max_order);
-	if (IS_ERR(folio))
-		return PTR_ERR(folio);
+	if (IS_ERR(folio)) {
+		r = PTR_ERR(folio);
+		goto out;
+	}
 
 	if (!folio_test_uptodate(folio)) {
 		clear_highpage(folio_page(folio, 0));
@@ -815,6 +886,8 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,
 	else
 		folio_put(folio);
 
+out:
+	filemap_invalidate_unlock_shared(file_inode(file)->i_mapping);
 	return r;
 }
 EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_gmem_get_pfn);
@@ -944,6 +1017,15 @@ static struct inode *kvm_gmem_alloc_inode(struct super_block *sb)
 
 	mpol_shared_policy_init(&gi->policy, NULL);
 
+	/*
+	 * Memory attributes are protected by the filemap invalidation lock, but
+	 * the lock structure isn't available at this time.  Immediately mark
+	 * maple tree as using external locking so that accessing the tree
+	 * before it's fully initialized results in NULL pointer dereferences
+	 * and not more subtle bugs.
+	 */
+	mt_init_flags(&gi->attributes, MT_FLAGS_LOCK_EXTERN | MT_FLAGS_USE_RCU);
+
 	gi->flags = 0;
 	INIT_LIST_HEAD(&gi->gmem_file_list);
 	return &gi->vfs_inode;
@@ -951,7 +1033,26 @@ static struct inode *kvm_gmem_alloc_inode(struct super_block *sb)
 
 static void kvm_gmem_destroy_inode(struct inode *inode)
 {
-	mpol_free_shared_policy(&GMEM_I(inode)->policy);
+	struct gmem_inode *gi = GMEM_I(inode);
+
+	mpol_free_shared_policy(&gi->policy);
+
+	/*
+	 * Note!  Checking for an empty tree is functionally necessary
+	 * to avoid explosions if the tree hasn't been fully
+	 * initialized, i.e. if the inode is being destroyed before
+	 * guest_memfd can set the external lock, lockdep would find
+	 * that the tree's internal ma_lock was not held.
+	 */
+	if (!mtree_empty(&gi->attributes)) {
+		/*
+		 * Acquire the invalidation lock purely to make lockdep happy,
+		 * the inode is unreachable at this point.
+		 */
+		filemap_invalidate_lock(inode->i_mapping);
+		__mt_destroy(&gi->attributes);
+		filemap_invalidate_unlock(inode->i_mapping);
+	}
 }
 
 static void kvm_gmem_free_inode(struct inode *inode)

-- 
2.54.0.563.g4f69b47b94-goog



^ permalink raw reply related

* [PATCH v6 00/43] guest_memfd: In-place conversion support
From: Ackerley Tng via B4 Relay @ 2026-05-07 20:22 UTC (permalink / raw)
  To: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
	ira.weiny, jmattson, jthoughton, michael.roth, oupton,
	pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
	steven.price, tabba, willy, wyihan, yan.y.zhao, forkloop,
	pratyush, suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
	Sean Christopherson, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, Steven Rostedt,
	Masami Hiramatsu, Mathieu Desnoyers, Jonathan Corbet, Shuah Khan,
	Shuah Khan, Vishal Annapurve, Andrew Morton, Chris Li,
	Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He, Barry Song,
	Axel Rasmussen, Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng,
	Shakeel Butt, Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka
  Cc: kvm, linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
	linux-mm, linux-coco, Ackerley Tng

This is v6 of guest_memfd in-place conversion support, now out of RFC.

Up till now, guest_memfd supports the entire inode worth of memory being
used as all-shared, or all-private. CoCo VMs may request guest memory to be
converted between private and shared states, and the only way to support
that currently would be to have the userspace VMM provide two sources of
backing memory from completely different areas of physical memory.

pKVM has a use case for in-place sharing: the guest and host may be
cooperating on given data, and pKVM doesn't protect data through
encryption, so copying that given data between different areas of physical
memory as part of conversions would be unnecessary work.

This series also serves as a foundation for guest_memfd huge page
support. Now, guest_memfd only supports PAGE_SIZE pages, so if two sources
of backing memory are used, the userspace VMM could maintain a steady total
memory utilized by punching out the pages that are not used. When huge
pages are available in guest_memfd, even if the backing memory source
supports hole punching within a huge page, punching out pages to maintain
the total memory utilized by a VM would be introducing lots of
fragmentation.

In-place conversion avoids fragmentation by allowing the same physical
memory to be used for both shared and private memory, with guest_memfd
tracks the shared/private status of all the pages at a per-page
granularity.

The central principle, which guest_memfd continues to uphold, is that any
guest-private page will not be mappable to host userspace. All pages will
be mmap()-able in host userspace, but accesses to guest-private pages (as
tracked by guest_memfd) will result in a SIGBUS.

This series introduces a guest_memfd ioctl (not kvm, vm or vcpu, but
guest_memfd ioctl) that allows userspace to set memory
attributes (shared/private) directly through the guest_memfd. This is the
appropriate interface because shared/private-ness is a property of memory
and hence the request should be sent directly to the memory provider -
guest_memfd.

Tested with both CONFIG_KVM_VM_MEMORY_ATTRIBUTES enabled and disabled:

+ tools/testing/selftests/kvm/guest_memfd_test.c
+ tools/testing/selftests/kvm/pre_fault_memory_test.c
+ tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
+ tools/testing/selftests/kvm/x86/private_mem_conversions_test.c
+ tools/testing/selftests/kvm/x86/private_mem_conversions_test.sh
+ tools/testing/selftests/kvm/x86/private_mem_kvm_exits_test.c

Updates for this revision:

+ Dropped everything to do with content modes (ZERO/PRESERVE) (thanks
  Michael and Sean!)
    + Discussed this at PUCK on 2026-05-06
    + guest_memfd was taking on too much complexity to try to paper
      over differences in how trusted firmware handle conversions
+ Addressed Liam's comments about usage of the maple_tree

TODOs

+ Resolve issue where guest_memfd_conversions_test, which uses the
  kselftest framework, doesn't perform teardown on assertion
  failure. Please see proposal at [9]
+ Test with TDX selftests. We're in the process of rebasing TDX selftests
  on this series and will post updates when that's tested.

I would like feedback on:

+ The use of private_mem_conversions_test.sh to run different options in
  private_mem_conversions_test. If this makes sense, I'll adjust the
  Makefile to have private_mem_conversions_test tested only via the script.

This series is based on kvm/next, and here's the tree for your convenience:

https://github.com/googleprodkernel/linux-cc/commits/guest_memfd-inplace-conversion-v6

Older series:

+ RFCv5 is at [8]
+ RFCv4 is at [7]
+ RFCv3 is at [6]
+ RFCv2 is at [5]
+ RFCv1 is at [4]
+ Previous versions of this feature, part of other series, are available at
  [1][2][3].

[1] https://lore.kernel.org/all/bd163de3118b626d1005aa88e71ef2fb72f0be0f.1726009989.git.ackerleytng@google.com/
[2] https://lore.kernel.org/all/20250117163001.2326672-6-tabba@google.com/
[3] https://lore.kernel.org/all/b784326e9ccae6a08388f1bf39db70a2204bdc51.1747264138.git.ackerleytng@google.com/
[4] https://lore.kernel.org/all/cover.1760731772.git.ackerleytng@google.com/T/
[5] https://lore.kernel.org/all/cover.1770071243.git.ackerleytng@google.com/T/
[6] https://lore.kernel.org/r/20260313-gmem-inplace-conversion-v3-0-5fc12a70ec89@google.com/T/
[7] https://lore.kernel.org/all/20260326-gmem-inplace-conversion-v4-0-e202fe950ffd@google.com/T/
[8] https://lore.kernel.org/r/20260428-gmem-inplace-conversion-v5-0-d8608ccfca22@google.com
[9] https://lore.kernel.org/all/20260414-selftest-global-metadata-v1-0-fd223922bc57@google.com/T/

Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
Ackerley Tng (25):
      KVM: x86/mmu: Bug the VM if gmem attributes are queried to determine max mapping level
      KVM: guest_memfd: Update kvm_gmem_populate() to use gmem attributes
      KVM: guest_memfd: Only prepare folios for private pages
      KVM: Move kvm_supported_mem_attributes() to kvm_host.h
      KVM: guest_memfd: Add base support for KVM_SET_MEMORY_ATTRIBUTES2
      KVM: guest_memfd: Ensure pages are not in use before conversion
      KVM: guest_memfd: Call arch invalidate hooks on conversion
      KVM: guest_memfd: Return early if range already has requested attributes
      KVM: guest_memfd: Advertise KVM_SET_MEMORY_ATTRIBUTES2 ioctl
      KVM: guest_memfd: Handle lru_add fbatch refcounts during conversion safety check
      KVM: guest_memfd: Use actual size for invalidation in kvm_gmem_release()
      KVM: guest_memfd: Determine invalidation filter from memory attributes
      KVM: TDX: Make source page optional for KVM_TDX_INIT_MEM_REGION
      KVM: selftests: Test basic single-page conversion flow
      KVM: selftests: Test conversion flow when INIT_SHARED
      KVM: selftests: Test conversion precision in guest_memfd
      KVM: selftests: Test conversion before allocation
      KVM: selftests: Convert with allocated folios in different layouts
      KVM: selftests: Test that truncation does not change shared/private status
      KVM: selftests: Test conversion with elevated page refcount
      KVM: selftests: Reset shared memory after hole-punching
      KVM: selftests: Provide function to look up guest_memfd details from gpa
      KVM: selftests: Make TEST_EXPECT_SIGBUS thread-safe
      KVM: selftests: Update private_mem_conversions_test to mmap() guest_memfd
      KVM: selftests: Add script to exercise private_mem_conversions_test

Michael Roth (1):
      KVM: SEV: Make 'uaddr' parameter optional for KVM_SEV_SNP_LAUNCH_UPDATE

Sean Christopherson (17):
      KVM: guest_memfd: Introduce per-gmem attributes, use to guard user mappings
      KVM: Rename KVM_GENERIC_MEMORY_ATTRIBUTES to KVM_VM_MEMORY_ATTRIBUTES
      KVM: Enumerate support for PRIVATE memory iff kvm_arch_has_private_mem is defined
      KVM: Stub in ability to disable per-VM memory attribute tracking
      KVM: guest_memfd: Wire up kvm_get_memory_attributes() to per-gmem attributes
      KVM: Move KVM_VM_MEMORY_ATTRIBUTES config definition to x86
      KVM: Let userspace disable per-VM mem attributes, enable per-gmem attributes
      KVM: guest_memfd: Enable INIT_SHARED on guest_memfd for x86 Coco VMs
      KVM: selftests: Create gmem fd before "regular" fd when adding memslot
      KVM: selftests: Rename guest_memfd{,_offset} to gmem_{fd,offset}
      KVM: selftests: Add support for mmap() on guest_memfd in core library
      KVM: selftests: Add selftests global for guest memory attributes capability
      KVM: selftests: Add helpers for calling ioctls on guest_memfd
      KVM: selftests: Test that shared/private status is consistent across processes
      KVM: selftests: Provide common function to set memory attributes
      KVM: selftests: Check fd/flags provided to mmap() when setting up memslot
      KVM: selftests: Update private memory exits test to work with per-gmem attributes

 Documentation/virt/kvm/api.rst                     |  78 +++-
 .../virt/kvm/x86/amd-memory-encryption.rst         |  15 +-
 Documentation/virt/kvm/x86/intel-tdx.rst           |   4 +
 arch/x86/include/asm/kvm_host.h                    |   2 +-
 arch/x86/kvm/Kconfig                               |  15 +-
 arch/x86/kvm/mmu/mmu.c                             |  13 +-
 arch/x86/kvm/svm/sev.c                             |  18 +-
 arch/x86/kvm/vmx/tdx.c                             |  11 +-
 arch/x86/kvm/x86.c                                 |  13 +-
 include/linux/kvm_host.h                           |  53 ++-
 include/trace/events/kvm.h                         |   4 +-
 include/uapi/linux/kvm.h                           |  16 +
 mm/swap.c                                          |   2 +
 tools/testing/selftests/kvm/Makefile.kvm           |   5 +
 tools/testing/selftests/kvm/include/kvm_util.h     | 138 +++++-
 tools/testing/selftests/kvm/include/test_util.h    |  34 +-
 .../selftests/kvm/kvm_has_gmem_attributes.c        |  17 +
 tools/testing/selftests/kvm/lib/kvm_util.c         | 133 +++---
 tools/testing/selftests/kvm/lib/test_util.c        |   7 -
 .../kvm/x86/guest_memfd_conversions_test.c         | 487 +++++++++++++++++++++
 .../kvm/x86/private_mem_conversions_test.c         |  53 ++-
 .../kvm/x86/private_mem_conversions_test.sh        | 128 ++++++
 .../selftests/kvm/x86/private_mem_kvm_exits_test.c |  36 +-
 virt/kvm/Kconfig                                   |   3 +-
 virt/kvm/guest_memfd.c                             | 457 +++++++++++++++++--
 virt/kvm/kvm_main.c                                |  82 +++-
 26 files changed, 1636 insertions(+), 188 deletions(-)
---
base-commit: 6d35786de28116ecf78797a62b84e6bf3c45aa5a
change-id: 20260225-gmem-inplace-conversion-bd0dbd39753a

Best regards,
--
Ackerley Tng <ackerleytng@google.com>



^ permalink raw reply

* Re: [PATCH v12 04/22] gpu: nova-core: mm: Add support to use PRAMIN windows to write to VRAM
From: Joel Fernandes @ 2026-05-07 19:44 UTC (permalink / raw)
  To: Alexandre Courbot
  Cc: linux-kernel, Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Dave Airlie, Daniel Almeida, dri-devel,
	rust-for-linux, nova-gpu, Nikola Djukic, David Airlie, Boqun Feng,
	John Hubbard, Alistair Popple, Timur Tabi, Edwin Peer,
	Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
	Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc
In-Reply-To: <DI8B0103X0HQ.7C99YLBMS2X5@nvidia.com>

Hi Alex,

On Sun, 03 May 2026, Alexandre Courbot wrote:
> `Pramin::new` takes different arguments (also check the other examples).

Fixed.

> The HRT series [1] will allow you to greatly simplify all this by
> storing a `&'a Bar0` directly in this structure. It will most likely
> land this cycle, so I think it's a good idea to proactively depend on
> them. It should apply cleanly (modulo the Tyr patches IIRC, but you can
> skip them if you don't build the driver) on top of `drm-rust-next` - on
> top of which this should also be rebased anyway.
>
> [1] https://lore.kernel.org/all/20260427221155.2144848-1-dakr@kernel.org/

Actually, I was going in the direction of making Bar0, GpuMm and Bar1
use Arc instead of lifetimes initially. The reason is, some of these are
long lived references. Directly using `&'a Bar0` means the lifetimes
also gets threaded to Pramin and then threads to GpuMm which is long
lived in some of my later patches. Some of the cases where we will have
long lived mappings are also vGPU and channels. I spoke to Danilo about
this, and we discussed that an initial approach could just use Arc since
refcounts are cheap and it should be just as performant, then once we
have the design ironed out, we can migrate to using lifetimes in this
code where appropriate. Even before HRT, I already used lifetimes and
analyzed the tradeoffs.

> I don't think that checking the range in this type is particularly
> useful, because this abstraction cannot validate VRAM ownership
> completely: VRAM can be sparse, reserved, or protected, and with the
> current bound access to these invalid areas is not prevented.

I think there's value in keeping this. The range check is similar in
spirit to what `Bar0` accessors do -- bounds are checked, but a passing
check doesn't guarantee the IO will succeed at the hardware level (an
address in a valid range could still cause issues). That's not a reason
to remove the guard; it still catches obvious bugs early. Having a sanity
check that says "this address is at least within the declared VRAM
region" is more useful than having no check at all, even if ownership
semantics aren't enforced at this layer.

>
> PRAMIN should only model the hardware windowing mechanism; address
> validity belongs to the higher layers that allocate or obtain those
> addresses. IIUC nothing will explode if we try to read or write into
> areas that are not VRAM.
> The problem with this lock is that it is buried, alongside its usage
> guide, deep into this type, ensuring that users won't ever see it. It
> will create deadlocks rather than preventing them.

Having an internal lock isn't inherently problematic -- the GPU buddy
allocator bindings follow the same pattern, for example. Let us go by usecase,
lets not complicate design based on a hypothetical. I wouldn't move the mutex
out of Pramin just based on guessing and complicate/thread the mutable reference
to layers that don't need to handle that complexity.

Further, what concrete design or use case requires mutex to be external? I
wouldn't add that complexity based on hypotheticals -- we go by actual use
cases, and there are actual users of this code in the patches that follow. For
what it's worth, I did try the outer-mutex approach myself before you mentioned
it and decided against it for this and the buddy code based on the reasons
mentioned.

> And when you look closer at it, you realize it is actually acquired for
> any use of `Pramin`, since the only method it exposes is `get_window`,
> which acquires the lock, and returns a `PraminWindow` which has exactly
> the same layout as `Pramin`, except that the lock is acquired.
>
> So what's the point? Let's just make `get_window` require a `&mut self`
> and let the owner of `Pramin` decide how to manage concurrent accesses.
> At least the doccomment for the lock will be visible from a higher
> layer.

The point is to keep it simple. What you're suggesting requires the
caller to do: lock + get_window + access window. My design is get_window
+ access window -- you need the latter two anyway, so the locking is
absorbed without any extra burden. Propagating lock outward adds
complexity at every layer that calls into `Pramin`; handling the lock
internally, as done elsewhere in the codebase, avoids that.

> Currently `Pramin` mixes elements of hardware access, memory management,
> and synchronization into the same type, squashing what should be
> different layers into a single one. Now that the `Io` trait is merged
> and available in `drm-rust-next`, it is a good time to implement the
> feedback I gave on v8. Roughly:
>
> - `Pramin` should just be the owner and arbiter of the
>   `NV_PBUS_BAR0_WINDOW` register. Its `get_window(&mut self,
>   window_base: Bounded<u64, 24>)` method simply returns a window to the
>   1MB area starting from `base << 16`. It's the simplest possible
>   abstraction.
> - The returned window implements `Io` and `IoKnownSize`. This will allow
>   users to do all the fancy stuff that comes with `Io`, including
>   projections.
> - Random accesses to VRAM, which are needed for walking the page tables
>   and updating them, can be built on top of this simple API. Either by
>   adding read/write ops directly to `Pramin`, or by defining another
>   type that owns a `&mut Pramin` and moves the window automatically if
>   the next access is out of bounds. This should result in window
>   management code that is simpler than the current `compute_window`.
>
> That's really all we need. Then `GpuMm` can manage concurrency similarly
> to the current model by wrapping `Pramin` inside a `Mutex`, and is also
> free to experiment with different locking strategies, something the
> current design doesn't allow.

I am looking into implementing `Io`/`IoKnownSize` on `PraminWindow` --
that part makes sense independently. The `Io` impl would operate on the
current fixed 1MB window view; the auto-repositioning methods stay as a
there is a usecase (more later). None of this requires the locking changes.

I'm familiar with the v8 feedback, but I disagree with parts of it. The
current design isn't complex -- it's the external locking + split into
multiple types approach that I think would make it more complex. I'm
adopting the parts of your suggestions that I agreed with.

The auto-repositioning is actively used and not hypothetical.
`install_mappings` (mm/pagetable/map.rs) holds a single `PraminWindow`
while writing PDEs at scattered buddy-allocated VRAM addresses and then
walking up to 4 PDE levels per VFN -- each level a separately-allocated
4KB page that can be many MB apart. `invalidate_ptes` similarly holds one
window across all VFNs: for a range mapping more than ~256 PT pages the
PTE region alone spans > 1MB. Removing auto-repositioning would push that
window arithmetic onto every caller.

> > +        // TODO: Convert to Bounded<u64, 40> when available.
>
> It's available. :)

Changed to use Bounded, thanks for that primitive.

> I think this method (and `write_window_base`) should be methods of the
> `NV_PBUS_BAR0_WINDOW` register (`window_base` and `set_window_base`)

Agreed, I will move it to the registers.

> > +        vram_offset: usize,
>
> This should be a `u64`, since it is a VRAM address.
>
> Actually, I noticed that you introduced a `VramAddress` type in a latter
> patch. It might be worth introducing it earlier and using it everywhere
> a VRAM address is involved, that way we cannot make any mistake - not
> critical for now, but feel free to do it if you think it helps.

Cool, I will look into it. Sounds reasonable to do.

thanks,
-- 
Joel Fernandes

^ permalink raw reply

* Re: [PATCH v13 3/4] gpio: rpmsg: add generic rpmsg GPIO driver
From: Shenwei Wang @ 2026-05-07 19:43 UTC (permalink / raw)
  To: Mathieu Poirier, Arnaud POULIQUEN
  Cc: Beleswar Prasad Padhi, Andrew Lunn, Linus Walleij,
	Bartosz Golaszewski, Jonathan Corbet, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Frank Li,
	Sascha Hauer, Shuah Khan, linux-gpio@vger.kernel.org,
	linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org,
	Pengutronix Kernel Team, Fabio Estevam, Peng Fan,
	devicetree@vger.kernel.org, linux-remoteproc@vger.kernel.org,
	imx@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
	dl-linux-imx, Bartosz Golaszewski
In-Reply-To: <afzIABSh1xtMEGbf@p14s>



> -----Original Message-----
> From: Mathieu Poirier <mathieu.poirier@linaro.org>
> Sent: Thursday, May 7, 2026 12:13 PM
> To: Arnaud POULIQUEN <arnaud.pouliquen@foss.st.com>
> Cc: Beleswar Prasad Padhi <b-padhi@ti.com>; Shenwei Wang
> <shenwei.wang@nxp.com>; Andrew Lunn <andrew@lunn.ch>; Linus Walleij
> <linusw@kernel.org>; Bartosz Golaszewski <brgl@kernel.org>; Jonathan Corbet
> <corbet@lwn.net>; Rob Herring <robh@kernel.org>; Krzysztof Kozlowski
> <krzk+dt@kernel.org>; Conor Dooley <conor+dt@kernel.org>; Bjorn Andersson
> <andersson@kernel.org>; Frank Li <frank.li@nxp.com>; Sascha Hauer
> <s.hauer@pengutronix.de>; Shuah Khan <skhan@linuxfoundation.org>; linux-
> gpio@vger.kernel.org; linux-doc@vger.kernel.org; linux-kernel@vger.kernel.org;
> Pengutronix Kernel Team <kernel@pengutronix.de>; Fabio Estevam
> <festevam@gmail.com>; Peng Fan <peng.fan@nxp.com>;
> devicetree@vger.kernel.org; linux-remoteproc@vger.kernel.org;
> imx@lists.linux.dev; linux-arm-kernel@lists.infradead.org; dl-linux-imx <linux-
> imx@nxp.com>; Bartosz Golaszewski <brgl@bgdev.pl>
> Subject: [EXT] Re: [PATCH v13 3/4] gpio: rpmsg: add generic rpmsg GPIO driver
> > > >  From my perspective, based on your proposal:
> > > >   1) Linux should send a get_config message to the remote proc (0x405 ->
> 0xD). 2) The remote processor would respond with the list of ports, associated
> > > >      with an remote endpoint addresses.
> > >
> > >
> > > Agreed, we can scale it for multiple remote endpoints like this.
> > >
> > > >   3) Linux would parse the response, compare it with the DT, enable the
> GPIO
> > > >      ports accordingly, creating it local endpoint and associating it with
> > > >      the remote endpoint.
> > > > Using name service to identify the ports should avoid step 1 & 2 ...
> > >
> > >
> > > Yes, but won't that make a lot of hard-codings in the driver?
> > >
> > > +static struct rpmsg_device_id rpmsg_gpio_channel_id_table[] = {
> > > +    { .name = "rpmsg-io-25" },
> > > +    { .name = "rpmsg-io-32" },
> > > +    { .name = "rpmsg-io-35" },
> > > +    { },
> > > +};
> > >
> > > What if tomorrow another vendor decides to add more remoteproc
> > > controlled GPIO ports to Linux, they would have to update this
> > > struct in the driver everytime. And the port indexes (25/32/35)
> > > could also differ between vendors. We should make the driver dynamic
> > > i.e. vendor agnostic.
> > >
> > > I think querying the remote firmware at runtime (step 1 & 2 above)
> > > is a common design pattern and makes the driver vendor agnostic. But
> > > feel free to correct me.
> > >
> >
> > You are right. My proposal would require a patch in rpmsg-core. The
> > idea of allowing a postfix in the compatible string has been discussed
> > before, but, if I remember correctly, it was not concluded.
> >
> 
> I also remember discussing this.  I even reviewed one of Arnaud's patch and
> submitted one myself.  This must have been in 2020 and the reason why it wasn't
> merged has escaped my memory.
> 
> > /* rpmsg devices and drivers are matched using the service name */
> > static inline int rpmsg_id_match(const struct rpmsg_device *rpdev,
> >                                 const struct rpmsg_device_id *id) {
> >       size_t len;
> >
> > +     len = strnlen(id->name, RPMSG_NAME_SIZE);
> > +     if (len && id->name[len - 1] == '*')
> > +             return !strncmp(id->name, rpdev->id.name, len - 1);
> >
> >       return strncmp(id->name, rpdev->id.name, RPMSG_NAME_SIZE) == 0;
> > }
> >
> > Then, in rpmsg-gpio, and possibly in other drivers such as rpmsg-tty
> > and a future rpmsg-i2c, we could use:
> > static struct rpmsg_device_id rpmsg_gpio_channel_id_table[] = {
> >     { .name = "rpmsg-io" },
> >     { .name = "rpmsg-io-*" },
> >     { },
> > };
> 
> That was my initial approach.  We don't even need an additional "rpmsg-io-*" in
> rpmsg_gpio_channel_id_table[].  All we need is:
> 
> /* rpmsg devices and drivers are matched using the service name */ static inline
> int rpmsg_id_match(const struct rpmsg_device *rpdev,
>                                  const struct rpmsg_device_id *id) {
>  +     size_t len = strnlen(id->name, RPMSG_NAME_SIZE);
> 
>  -     return strncmp(id->name, rpdev->id.name, RPMSG_NAME_SIZE) == 0;
>  +     return strncmp(id->name, rpdev->id.name, len) == 0;
> }
> 

If we encode the port index directly into ept->src, for example:

    ept->src = (baseaddr << 8) | port_index;

where baseaddr can be derived from the channel address, we can avoid the possible address conflict.

With this approach, the patch to rpmsg-core would no longer be necessary.

Thanks,
Shenwei

> And let the rpmsg-virtio-gpio driver parse @rpdev->id.name to match with a
> GPIO controller in the DT.
> 
> >
> > If exact name matching is strongly required, then this proposal would
> > not be suitablea.
> >
> > A third option would be a combination of both approaches: instantiate
> > the device using the same name service from the remote side, as done
> > in rpmsg-tty. In that case, a get_config message, or a similar
> > mechanism, would also be needed to retrieve the port information from the
> remote side.
> >
> 
> I'm not overly fond of a get_config message because it is one more thing we have
> to define and maintain.
> 
> Arnaud: is there a get_config message already defined for rpmsg_tty?
> 
> Beleswar: Can you provide a link to a virtio device that would use a get_config
> message?
> 
> > Tanmaya also proposed another alternative based on reserved addresses.
> >
> > At this point, I suggest letting Mathieu review the discussion and
> > recommend the most suitable approach.
> >
> > Thanks,
> > Arnaud
> >
> > > >
> > > > At the end, whatever solution is implemented, my main concern is
> > > > that the Linux driver design should, if possible, avoid adding
> > > > unnecessary complexity or limitations on the remote side (for instance in
> openAMP project).
> > >
> > >
> > > Yes definitely, I want the same. Feel free to let me know if this
> > > does not suit with the OpenAMP project.
> > >
> > > Thanks,
> > > Beleswar
> > >
> > > >
> > > > Thanks,
> > > > Arnaud
> > > >
> > > >
> > > > > So Linux does not need to send the port idx everytime while
> > > > > sending a gpio message anymore.
> > > > >
> > > > > Thanks,
> > > > > Beleswar
> > > > >
> > > > > [...]
> > > > >
> > > >
> >

^ permalink raw reply

* Re: [PATCH v5 07/11] leds: flash: add support for Samsung S2M series PMIC flash LED device
From: Jacek Anaszewski @ 2026-05-07 19:39 UTC (permalink / raw)
  To: Lee Jones, Kaustabh Chakraborty
  Cc: Pavel Machek, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	MyungJoo Ham, Chanwoo Choi, Sebastian Reichel,
	Krzysztof Kozlowski, André Draszik, Alexandre Belloni,
	Jonathan Corbet, Shuah Khan, Nam Tran,
	Łukasz Lebiedziński, linux-leds, devicetree,
	linux-kernel, linux-pm, linux-samsung-soc, linux-rtc, linux-doc
In-Reply-To: <20260507164654.GS305027@google.com>

Hi Lee,

On 5/7/26 6:46 PM, Lee Jones wrote:
> On Fri, 24 Apr 2026, Kaustabh Chakraborty wrote:
> 
>> Add support for flash LEDs in certain Samsung S2M series PMICs.
>> The device has two channels for LEDs, typically for the back and front
>> cameras in mobile devices. Both channels can be independently
>> controlled, and can be operated in torch or flash modes.
>>
>> The driver includes initial support for the S2MU005 PMIC flash LEDs.
>>
>> Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
>> ---
>>   drivers/leds/flash/Kconfig          |  12 ++
>>   drivers/leds/flash/Makefile         |   1 +
>>   drivers/leds/flash/leds-s2m-flash.c | 358 ++++++++++++++++++++++++++++++++++++
>>   3 files changed, 371 insertions(+)
>>
>> diff --git a/drivers/leds/flash/Kconfig b/drivers/leds/flash/Kconfig
>> index 5e08102a67841..be62e05277429 100644
>> --- a/drivers/leds/flash/Kconfig
>> +++ b/drivers/leds/flash/Kconfig
>> @@ -114,6 +114,18 @@ config LEDS_RT8515
>>   	  To compile this driver as a module, choose M here: the module
>>   	  will be called leds-rt8515.
>>   
>> +config LEDS_S2M_FLASH
>> +	tristate "Samsung S2M series PMICs flash/torch LED support"
>> +	depends on LEDS_CLASS
>> +	depends on MFD_SEC_CORE
>> +	depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
> 
> The `|| !V4L2_FLASH_LED_CLASS` part of this dependency makes it
> unconditionally true. Was this intended? Perhaps this dependency can be
> removed entirely.
This is for a reason to allow building the driver if
V4L2_FLASH_LED_CLASS is turned off, or build it as a module
if V4L2_FLASH_LED_CLASS=m. You will get nice explanation from
Google AI if you type just
"V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS".

See e.g. [0], which fixes undefined symbol error by adding this.

[0] 
https://git.paulk.fr/projects/linux.git/commit/drivers?h=sunxi/cedrus/jpeg-nv16&id=dbeb02a0bc41b9e9b9c05e460890351efecf1352

-- 
Best regards,
Jacek Anaszewski


^ permalink raw reply

* Re: [PATCH] Documentation: fix typo and formattting in security/credentials.rst
From: Mayank Gite @ 2026-05-07 19:35 UTC (permalink / raw)
  To: Jonathan Corbet
  Cc: Randy Dunlap, Paul Moore, Serge Hallyn, Shuah Khan,
	linux-security-module, linux-doc, linux-kernel
In-Reply-To: <87pl378417.fsf@trenco.lwn.net>

On Thu, May 07, 2026 at 12:31:48PM -0600, Jonathan Corbet wrote:
> John Doe <drapl0n.kernel@gmail.com> writes:
> 
> > Thank you for the feedback and acknowledgment. I thought that since my
> > earlier patch was invalid/corrupted, I should submit a new one rather than
> > version 2.
> >
> > I understand the procedure now and will ensure that future revisions follow
> > the guidelines.
> 
> Along those lines, seeing multiple names associated with the same email
> account is a bit disconcerting.  We are not generally interested in
> taking patches submitted under pseudonyms...

While configuring mutt, I forgot to change the default placeholder value
for the "realname" variable, resulting in an inconsistent sender name.

I have corrected the configuration and verified that my environment is
now set up appropriately.

Sorry for the confusion.

Mayank

^ permalink raw reply

* Re: [v6 00/10] Reintroduce Hornet LSM
From: Paul Moore @ 2026-05-07 19:19 UTC (permalink / raw)
  To: Blaise Boscaccy
  Cc: Jonathan Corbet, James Morris, Serge E. Hallyn,
	Mickaël Salaün, Günther Noack,
	Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
	Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
	linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260429191431.2345448-1-bboscaccy@linux.microsoft.com>

On Wed, Apr 29, 2026 at 3:14 PM Blaise Boscaccy
<bboscaccy@linux.microsoft.com> wrote:
>
> This patch series introduces the next iteration of the Hornet LSM.
> Hornet’s goal is to provide a secure and extensible in-kernel
> signature verification mechanism for eBPF programs.

I see that Fan identified a few issues that need resolution, but I
just wanted to make sure you've read the expectations for a new LSM.
To be clear, I think you've ticked all the boxes, and there is a
MAINTAINERS entry with your name attached, but I just wanted to make
sure you're okay with maintaining Hornet.  I like Hornet, I think it's
a nice and fairly clever solution, but the last thing I need is a new
LSM to maintain :)

https://github.com/LinuxSecurityModule/kernel#new-lsms

--
paul-moore.com

^ permalink raw reply

* [PATCH v7 10/10] ipe: Add BPF program load policy enforcement via Hornet integration
From: Blaise Boscaccy @ 2026-05-07 19:14 UTC (permalink / raw)
  To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
	Serge E. Hallyn, Mickaël Salaün, Günther Noack,
	Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
	Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
	linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260507191416.2984054-1-bboscaccy@linux.microsoft.com>

Add support for the bpf_prog_load_post_integrity LSM hook, enabling IPE
to make policy decisions about BPF program loading based on integrity
verdicts provided by the Hornet LSM.

New policy operation:
  op=BPF_PROG_LOAD - Matches BPF program load events

New policy properties:
  bpf_signature=NONE      - No Verdict
  bpf_signature=OK        - Program signature and map hashes verified
  bpf_signature=UNSIGNED  - No signature provided
  bpf_signature=PARTIALSIG - Signature OK but no map hash data
  bpf_signature=UNKNOWNKEY - The keyring requested by the user is invalid
  bpf_signature=UNEXPECTED - An unexpected hash value was encountered
  bpf_signature=FAULT 	   - System error during verification
  bpf_signature=BADSIG    - Signature or map hash verification failed
  bpf_keyring=BUILTIN     - Program was signed using a builtin keyring
  bpf_keyring=SECONDARY   - Program was signed using the secondary keyring
  bpf_keyring=PLATFORM    - Program was signed using the platform keyring
  bpf_kernel=TRUE         - Program originated from kernelspace
  bpf_kernel=FALSE        - Program originated from userspace

These properties map directly to the lsm_integrity_verdict enum values
provided by the Hornet LSM through security_bpf_prog_load_post_integrity.

The feature is gated on CONFIG_IPE_PROP_BPF_SIGNATURE which depends on
CONFIG_SECURITY_HORNET.

Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
 Documentation/admin-guide/LSM/ipe.rst | 162 +++++++++++++++++++++++++-
 Documentation/security/ipe.rst        |  68 +++++++++++
 security/ipe/Kconfig                  |  15 +++
 security/ipe/audit.c                  |  15 +++
 security/ipe/eval.c                   |  93 ++++++++++++++-
 security/ipe/eval.h                   |  11 ++
 security/ipe/hooks.c                  |  63 ++++++++++
 security/ipe/hooks.h                  |  15 +++
 security/ipe/ipe.c                    |  14 +++
 security/ipe/ipe.h                    |   3 +
 security/ipe/policy.h                 |  14 +++
 security/ipe/policy_parser.c          |  27 +++++
 12 files changed, 498 insertions(+), 2 deletions(-)

diff --git a/Documentation/admin-guide/LSM/ipe.rst b/Documentation/admin-guide/LSM/ipe.rst
index a756d81585317..d68ba9d98859e 100644
--- a/Documentation/admin-guide/LSM/ipe.rst
+++ b/Documentation/admin-guide/LSM/ipe.rst
@@ -559,7 +559,8 @@ policy. Two properties are built-into the policy parser: 'op' and 'action'.
 The other properties are used to restrict immutable security properties
 about the files being evaluated. Currently those properties are:
 '``boot_verified``', '``dmverity_signature``', '``dmverity_roothash``',
-'``fsverity_signature``', '``fsverity_digest``'. A description of all
+'``fsverity_signature``', '``fsverity_digest``', '``bpf_signature``',
+'``bpf_keyring``', '``bpf_kernel``'. A description of all
 properties supported by IPE are listed below:
 
 op
@@ -603,6 +604,14 @@ as the first token. IPE supports the following operations:
       Controls loading IMA certificates through the Kconfigs,
       ``CONFIG_IMA_X509_PATH`` and ``CONFIG_EVM_X509_PATH``.
 
+   ``BPF_PROG_LOAD``:
+
+      Pertains to BPF programs being loaded via the ``bpf()`` syscall.
+      This operation is used in conjunction with the ``bpf_signature``,
+      ``bpf_keyring``, and ``bpf_kernel`` properties to control BPF
+      program loading based on integrity verification provided by the
+      Hornet LSM.
+
 action
 ~~~~~~
 
@@ -713,6 +722,105 @@ fsverity_signature
 
       fsverity_signature=(TRUE|FALSE)
 
+bpf_signature
+~~~~~~~~~~~~~
+
+   This property can be utilized for authorization of BPF program loads based
+   on the integrity verdict provided by the Hornet LSM. When a BPF program is
+   loaded, Hornet performs cryptographic verification of the program's PKCS#7
+   signature (if present) and passes an integrity verdict to IPE via the
+   ``security_bpf_prog_load_post_integrity`` hook. IPE can then allow or deny
+   the load based on the verdict.
+
+   This property depends on ``SECURITY_HORNET`` and is controlled by the
+   ``IPE_PROP_BPF_SIGNATURE`` config option.
+   The format of this property is::
+
+      bpf_signature=(NONE|OK|UNSIGNED|PARTIALSIG|UNKNOWNKEY|UNEXPECTED|FAULT|BADSIG)
+
+   The possible values correspond to the integrity verdicts from Hornet:
+
+      ``NONE``
+
+         No integrity verdict was set (default/uninitialized).
+
+      ``OK``
+
+         The BPF program's signature and all map hashes were successfully
+         verified.
+
+      ``UNSIGNED``
+
+         No signature was provided with the BPF program.
+
+      ``PARTIALSIG``
+
+         The program signature was verified, but no authenticated map hash
+         data was present.
+
+      ``UNKNOWNKEY``
+
+         The keyring requested by the user is invalid.
+
+      ``UNEXPECTED``
+
+         An unexpected map hash value was encountered during verification.
+
+      ``FAULT``
+
+         A system error occurred during signature verification.
+
+      ``BADSIG``
+
+         The signature or hash verification failed.
+
+bpf_keyring
+~~~~~~~~~~~~
+
+   This property can be utilized for authorization of BPF program loads based
+   on the keyring specified in the ``bpf_attr`` during the ``BPF_PROG_LOAD``
+   syscall. This allows policies to restrict which keyring must be used for
+   signature verification of BPF programs.
+
+   This property shares the ``IPE_PROP_BPF_SIGNATURE`` config option with
+   ``bpf_signature``.
+   The format of this property is::
+
+      bpf_keyring=(BUILTIN|SECONDARY|PLATFORM)
+
+   The possible values correspond to the system keyrings:
+
+      ``BUILTIN``
+
+         The builtin trusted keyring (``.builtin_trusted_keys``), which
+         contains keys embedded at kernel compile time.
+
+      ``SECONDARY``
+
+         The secondary trusted keyring (``.secondary_trusted_keys``), which
+         includes both builtin trusted keys and keys added at runtime.
+
+      ``PLATFORM``
+
+         The platform keyring (``.platform``), which contains keys provided
+         by the platform firmware (e.g. UEFI db keys).
+
+bpf_kernel
+~~~~~~~~~~
+
+   This property can be utilized for authorization of BPF program loads based
+   on whether the load originated from kernel space or user space. The BPF
+   light skeleton infrastructure performs a secondary kernel-originated program
+   load that will not carry a signature. This property allows policies to
+   permit such kernel-originated loads while still requiring signatures for
+   user-space loads.
+
+   This property shares the ``IPE_PROP_BPF_SIGNATURE`` config option with
+   ``bpf_signature``.
+   The format of this property is::
+
+      bpf_kernel=(TRUE|FALSE)
+
 Policy Examples
 ---------------
 
@@ -788,6 +896,58 @@ Allow execution of a specific fs-verity file
 
    op=EXECUTE fsverity_digest=sha256:fd88f2b8824e197f850bf4c5109bea5cf0ee38104f710843bb72da796ba5af9e action=ALLOW
 
+Allow only signed BPF programs
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+   policy_name=Allow_Signed_BPF policy_version=0.0.0
+   DEFAULT action=ALLOW
+
+   DEFAULT op=BPF_PROG_LOAD action=DENY
+   op=BPF_PROG_LOAD bpf_kernel=TRUE action=ALLOW
+   op=BPF_PROG_LOAD bpf_signature=OK action=ALLOW
+
+This policy allows all other operations but restricts BPF program loading
+to only programs that either originate from kernel space (e.g. light skeleton
+reloads) or have a valid signature verified by the Hornet LSM. Unsigned or
+improperly signed BPF programs from user space will be denied.
+
+Allow signed BPF programs from a specific keyring
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+   policy_name=Allow_BPF_Builtin_Keyring policy_version=0.0.0
+   DEFAULT action=ALLOW
+
+   DEFAULT op=BPF_PROG_LOAD action=DENY
+   op=BPF_PROG_LOAD bpf_kernel=TRUE action=ALLOW
+   op=BPF_PROG_LOAD bpf_signature=OK bpf_keyring=BUILTIN action=ALLOW
+
+This policy further restricts BPF program loading to only accept programs
+whose signatures were verified using the builtin trusted keyring. Programs
+signed against the secondary or platform keyrings will be denied, providing
+tighter control over which signing keys are acceptable.
+
+Allow signed BPF programs with relaxed partial signatures
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+   policy_name=Allow_BPF_Partial policy_version=0.0.0
+   DEFAULT action=ALLOW
+
+   DEFAULT op=BPF_PROG_LOAD action=DENY
+   op=BPF_PROG_LOAD bpf_kernel=TRUE action=ALLOW
+   op=BPF_PROG_LOAD bpf_signature=OK action=ALLOW
+   op=BPF_PROG_LOAD bpf_signature=PARTIALSIG action=ALLOW
+
+This policy allows BPF programs that have been fully verified (``OK``) as
+well as programs with a valid program signature but without authenticated
+map hash data (``PARTIALSIG``). This can be useful during development or
+for programs that do not use maps.
+
 Additional Information
 ----------------------
 
diff --git a/Documentation/security/ipe.rst b/Documentation/security/ipe.rst
index 4a7d953abcdc3..2363734e934f0 100644
--- a/Documentation/security/ipe.rst
+++ b/Documentation/security/ipe.rst
@@ -412,6 +412,73 @@ a standard securityfs policy tree::
 
 The policy is stored in the ``->i_private`` data of the MyPolicy inode.
 
+BPF/Hornet Integration
+~~~~~~~~~~~~~~~~~~~~~~
+
+IPE integrates with the Hornet LSM to enforce integrity policies on BPF
+program loading. Hornet performs cryptographic verification of BPF program
+signatures (PKCS#7 with authenticated attributes containing map hashes)
+and produces an ``enum lsm_integrity_verdict``. IPE acts as the policy
+enforcer: it stores Hornet's verdict in a per-program LSM blob and later
+evaluates it against the active IPE policy when the BPF program load is
+finalized.
+
+Enforcement is split across two LSM hooks so that signature verification
+and policy evaluation are cleanly separated. This also lets IPE evaluate
+policy uniformly even when no integrity provider ran.
+
+The hook flow is:
+
+  1. User space (or the kernel, via the BPF light skeleton) invokes
+     ``BPF_PROG_LOAD`` through the ``bpf()`` syscall.
+  2. Hornet's ``bpf_prog_load_integrity`` hook runs first and calls
+     ``hornet_check_program()`` to verify the program's PKCS#7 signature
+     against ``attr->keyring_id`` and to check the signed map hashes
+     (decoded from the ``OID_hornet_data`` authenticated attribute)
+     against the live hashes of the frozen maps referenced from
+     ``attr->fd_array``. The function produces one of
+     ``LSM_INT_VERDICT_OK``, ``LSM_INT_VERDICT_UNSIGNED``,
+     ``LSM_INT_VERDICT_BADSIG``, ``LSM_INT_VERDICT_PARTIALSIG``,
+     ``LSM_INT_VERDICT_UNKNOWNKEY``, ``LSM_INT_VERDICT_UNEXPECTED``, or
+     ``LSM_INT_VERDICT_FAULT``.
+  3. Hornet calls ``security_bpf_prog_load_post_integrity()`` with the
+     resulting verdict and its ``lsm_id``. IPE's
+     ``ipe_bpf_prog_load_post_integrity`` handler does **not** enforce
+     policy here; it only stashes the verdict in IPE's per-program blob
+     and returns ``0``. This keeps the integrity step decoupled from
+     policy evaluation and allows multiple providers to coexist without
+     short-circuiting each other.
+  4. The core BPF load path then invokes the standard ``bpf_prog_load``
+     LSM hook. IPE's ``ipe_bpf_prog_load`` reads the verdict back out of
+     the per-program blob, populates an ``ipe_eval_ctx``, and calls
+     ``ipe_evaluate_event()`` against the active policy's
+     ``BPF_PROG_LOAD`` rules. A deny verdict returns ``-EACCES`` and
+     aborts the load.
+
+If no integrity provider populated the blob (e.g. Hornet is not enabled,
+or the load came from a path Hornet does not cover), the verdict defaults
+to ``LSM_INT_VERDICT_NONE`` and IPE evaluates accordingly. Policy can
+therefore express "deny anything Hornet did not vouch for".
+
+Three properties are available for BPF policy rules:
+
+  - ``bpf_signature``: Matches against the integrity verdict (OK, UNSIGNED,
+    BADSIG, etc.)
+  - ``bpf_keyring``: Matches against the keyring specified in ``bpf_attr``
+    (BUILTIN, SECONDARY, PLATFORM)
+  - ``bpf_kernel``: Matches whether the load originated from kernel space
+    (TRUE/FALSE). This is important because the BPF light skeleton
+    infrastructure performs a secondary kernel-originated program load that
+    does not carry a signature.
+
+All three properties are gated on ``CONFIG_IPE_PROP_BPF_SIGNATURE`` which
+depends on ``CONFIG_SECURITY_HORNET``.
+
+The evaluation context (``struct ipe_eval_ctx``) carries three BPF-specific
+fields: ``bpf_verdict`` (the integrity verdict enum), ``bpf_keyring_id``
+(the ``s32`` keyring ID from ``bpf_attr``), and ``bpf_kernel`` (bool
+indicating kernel origin).
+
 Tests
 -----
 
@@ -439,6 +506,7 @@ IPE has KUnit Tests for the policy parser. Recommended kunitconfig::
   CONFIG_IPE_PROP_DM_VERITY_SIGNATURE=y
   CONFIG_IPE_PROP_FS_VERITY=y
   CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG=y
+  CONFIG_IPE_PROP_BPF_SIGNATURE=y
   CONFIG_SECURITY_IPE_KUNIT_TEST=y
 
 In addition, IPE has a python based integration
diff --git a/security/ipe/Kconfig b/security/ipe/Kconfig
index a110a6cd848b7..95775139612df 100644
--- a/security/ipe/Kconfig
+++ b/security/ipe/Kconfig
@@ -95,6 +95,21 @@ config IPE_PROP_FS_VERITY_BUILTIN_SIG
 
 	  if unsure, answer Y.
 
+config IPE_PROP_BPF_SIGNATURE
+	bool "Enable support for Hornet BPF program signature verification"
+	depends on SECURITY_HORNET
+	help
+	  This option enables the 'bpf_signature', 'bpf_kernel' and
+	  'bpf_keyring' properties within IPE policies. The
+	  'bpf_signature' property allows IPE to make policy decisions
+	  based on the integrity verdict provided by the Hornet LSM
+	  when a BPF program is loaded.  Verdicts include OK,
+	  UNSIGNED, PARTIALSIG, BADSIG, and others.  The 'bpf_keyring'
+	  property allows policies to match against the keyring
+	  specified in bpf_attr (BUILTIN, SECONDARY, PLATFORM).
+
+	  If unsure, answer Y.
+
 endmenu
 
 config SECURITY_IPE_KUNIT_TEST
diff --git a/security/ipe/audit.c b/security/ipe/audit.c
index 3f0deeb549127..251c6ec2f8423 100644
--- a/security/ipe/audit.c
+++ b/security/ipe/audit.c
@@ -41,6 +41,7 @@ static const char *const audit_op_names[__IPE_OP_MAX + 1] = {
 	"KEXEC_INITRAMFS",
 	"POLICY",
 	"X509_CERT",
+	"BPF_PROG_LOAD",
 	"UNKNOWN",
 };
 
@@ -51,6 +52,7 @@ static const char *const audit_hook_names[__IPE_HOOK_MAX] = {
 	"MPROTECT",
 	"KERNEL_READ",
 	"KERNEL_LOAD",
+	"BPF_PROG_LOAD",
 };
 
 static const char *const audit_prop_names[__IPE_PROP_MAX] = {
@@ -62,6 +64,19 @@ static const char *const audit_prop_names[__IPE_PROP_MAX] = {
 	"fsverity_digest=",
 	"fsverity_signature=FALSE",
 	"fsverity_signature=TRUE",
+	"bpf_signature=NONE",
+	"bpf_signature=OK",
+	"bpf_signature=UNSIGNED",
+	"bpf_signature=PARTIALSIG",
+	"bpf_signature=UNKNOWNKEY",
+	"bpf_signature=UNEXPECTED",
+	"bpf_signature=FAULT",
+	"bpf_signature=BADSIG",
+	"bpf_keyring=BUILTIN",
+	"bpf_keyring=SECONDARY",
+	"bpf_keyring=PLATFORM",
+	"bpf_kernel=FALSE",
+	"bpf_kernel=TRUE",
 };
 
 /**
diff --git a/security/ipe/eval.c b/security/ipe/eval.c
index 21439c5be3364..705c4ecfda696 100644
--- a/security/ipe/eval.c
+++ b/security/ipe/eval.c
@@ -11,6 +11,7 @@
 #include <linux/rcupdate.h>
 #include <linux/moduleparam.h>
 #include <linux/fsverity.h>
+#include <linux/verification.h>
 
 #include "ipe.h"
 #include "eval.h"
@@ -265,8 +266,72 @@ static bool evaluate_fsv_sig_true(const struct ipe_eval_ctx *const ctx)
 }
 #endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
 
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+/**
+ * evaluate_bpf_sig() - Evaluate @ctx against a bpf_signature property.
+ * @ctx: Supplies a pointer to the context being evaluated.
+ * @expected: The expected lsm_integrity_verdict to match against.
+ *
+ * Return:
+ * * %true	- The current @ctx matches the expected verdict
+ * * %false	- The current @ctx doesn't match the expected verdict
+ */
+static bool evaluate_bpf_sig(const struct ipe_eval_ctx *const ctx,
+			     enum lsm_integrity_verdict expected)
+{
+	return ctx->bpf_verdict == expected;
+}
+#else
+static bool evaluate_bpf_sig(const struct ipe_eval_ctx *const ctx,
+			     enum lsm_integrity_verdict expected)
+{
+	return false;
+}
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
+
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+/**
+ * evaluate_bpf_keyring() - Evaluate @ctx against a bpf_keyring property.
+ * @ctx: Supplies a pointer to the context being evaluated.
+ * @expected: The expected keyring_id to match against.
+ *
+ * Return:
+ * * %true	- The current @ctx matches the expected keyring
+ * * %false	- The current @ctx doesn't match the expected keyring
+ */
+static bool evaluate_bpf_keyring(const struct ipe_eval_ctx *const ctx,
+				 s32 expected)
+{
+	return ctx->bpf_keyring_id == expected;
+}
+#else
+static bool evaluate_bpf_keyring(const struct ipe_eval_ctx *const ctx,
+				 s32 expected)
+{
+	return false;
+}
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
+
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+/**
+ * evaluate_bpf_kernel() - Evaluate @ctx against the bpf_kernel property.
+ * @ctx: Supplies a pointer to the context being evaluated.
+ *
+ * Return:
+ * * %true	- The current @ctx bpf_kernel property is true
+ * * %false	- The current @ctx bpf_kernel property is false
+ */
+static bool evaluate_bpf_kernel(const struct ipe_eval_ctx *const ctx)
+{
+	return ctx->bpf_kernel;
+}
+#else
+static bool evaluate_bpf_kernel(const struct ipe_eval_ctx *const ctx)
+{
+	return false;
+}
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
 /**
- * evaluate_property() - Analyze @ctx against a rule property.
  * @ctx: Supplies a pointer to the context to be evaluated.
  * @p: Supplies a pointer to the property to be evaluated.
  *
@@ -297,6 +362,32 @@ static bool evaluate_property(const struct ipe_eval_ctx *const ctx,
 		return evaluate_fsv_sig_false(ctx);
 	case IPE_PROP_FSV_SIG_TRUE:
 		return evaluate_fsv_sig_true(ctx);
+	case IPE_PROP_BPF_SIG_NONE:
+		return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_NONE);
+	case IPE_PROP_BPF_SIG_OK:
+		return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_OK);
+	case IPE_PROP_BPF_SIG_UNSIGNED:
+		return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_UNSIGNED);
+	case IPE_PROP_BPF_SIG_PARTIALSIG:
+		return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_PARTIALSIG);
+	case IPE_PROP_BPF_SIG_UNKNOWNKEY:
+		return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_UNKNOWNKEY);
+	case IPE_PROP_BPF_SIG_UNEXPECTED:
+		return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_UNEXPECTED);
+	case IPE_PROP_BPF_SIG_FAULT:
+		return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_FAULT);
+	case IPE_PROP_BPF_SIG_BADSIG:
+		return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_BADSIG);
+	case IPE_PROP_BPF_KEYRING_BUILTIN:
+		return evaluate_bpf_keyring(ctx, 0);
+	case IPE_PROP_BPF_KEYRING_SECONDARY:
+		return evaluate_bpf_keyring(ctx, (s32)(unsigned long)VERIFY_USE_SECONDARY_KEYRING);
+	case IPE_PROP_BPF_KEYRING_PLATFORM:
+		return evaluate_bpf_keyring(ctx, (s32)(unsigned long)VERIFY_USE_PLATFORM_KEYRING);
+	case IPE_PROP_BPF_KERNEL_FALSE:
+		return !evaluate_bpf_kernel(ctx);
+	case IPE_PROP_BPF_KERNEL_TRUE:
+		return evaluate_bpf_kernel(ctx);
 	default:
 		return false;
 	}
diff --git a/security/ipe/eval.h b/security/ipe/eval.h
index fef65a36468cb..b061cb5ade27e 100644
--- a/security/ipe/eval.h
+++ b/security/ipe/eval.h
@@ -37,6 +37,12 @@ struct ipe_inode {
 };
 #endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
 
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+struct ipe_bpf_prog {
+	enum lsm_integrity_verdict verdict;
+};
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
+
 struct ipe_eval_ctx {
 	enum ipe_op_type op;
 	enum ipe_hook_type hook;
@@ -52,6 +58,11 @@ struct ipe_eval_ctx {
 #ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG
 	const struct ipe_inode *ipe_inode;
 #endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+	enum lsm_integrity_verdict bpf_verdict;
+	s32 bpf_keyring_id;
+	bool bpf_kernel;
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
 };
 
 enum ipe_match {
diff --git a/security/ipe/hooks.c b/security/ipe/hooks.c
index 0ae54a880405a..9271e129a2cf2 100644
--- a/security/ipe/hooks.c
+++ b/security/ipe/hooks.c
@@ -340,3 +340,66 @@ int ipe_inode_setintegrity(const struct inode *inode,
 	return -EINVAL;
 }
 #endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
+
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+/**
+ * ipe_bpf_prog_load_post_integrity() - Store integrity verdict in per-prog blob.
+ * @prog: Supplies the BPF program being loaded.
+ * @attr: Supplies the bpf syscall attributes.
+ * @token: Supplies the BPF token, if any.
+ * @kernel: Whether the call originated from the kernel.
+ * @lsmid: Supplies the LSM ID of the integrity provider.
+ * @verdict: Supplies the integrity verdict from the provider (e.g. Hornet).
+ *
+ * This hook stores the integrity verdict in IPE's per-prog security blob
+ * so that ipe_bpf_prog_load() can later read it for policy evaluation.
+ *
+ * Return:
+ * * %0		- Always succeeds (policy is evaluated in bpf_prog_load)
+ */
+int ipe_bpf_prog_load_post_integrity(struct bpf_prog *prog,
+				     union bpf_attr *attr,
+				     struct bpf_token *token,
+				     bool kernel,
+				     const struct lsm_id *lsmid,
+				     enum lsm_integrity_verdict verdict)
+{
+	struct ipe_bpf_prog *blob = ipe_bpf_prog(prog);
+
+	blob->verdict = verdict;
+
+	return 0;
+}
+
+/**
+ * ipe_bpf_prog_load() - IPE policy evaluation for BPF program load.
+ * @prog: Supplies the BPF program being loaded.
+ * @attr: Supplies the bpf syscall attributes.
+ * @token: Supplies the BPF token, if any.
+ * @kernel: Whether the call originated from the kernel.
+ *
+ * Reads the integrity verdict previously stored by post_integrity (if any)
+ * and evaluates IPE policy. If no integrity provider ran, the verdict
+ * defaults to LSM_INT_VERDICT_NONE.
+ *
+ * Return:
+ * * %0		- Success
+ * * %-EACCES	- Did not pass IPE policy
+ */
+int ipe_bpf_prog_load(struct bpf_prog *prog,
+		      union bpf_attr *attr,
+		      struct bpf_token *token,
+		      bool kernel)
+{
+	struct ipe_bpf_prog *blob = ipe_bpf_prog(prog);
+	struct ipe_eval_ctx ctx = IPE_EVAL_CTX_INIT;
+
+	ctx.op = IPE_OP_BPF_PROG_LOAD;
+	ctx.hook = IPE_HOOK_BPF_PROG_LOAD;
+	ctx.bpf_verdict = blob->verdict;
+	ctx.bpf_keyring_id = attr->keyring_id;
+	ctx.bpf_kernel = kernel;
+
+	return ipe_evaluate_event(&ctx);
+}
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
diff --git a/security/ipe/hooks.h b/security/ipe/hooks.h
index 07db373327402..8a6d1a459e00c 100644
--- a/security/ipe/hooks.h
+++ b/security/ipe/hooks.h
@@ -10,6 +10,7 @@
 #include <linux/security.h>
 #include <linux/blk_types.h>
 #include <linux/fsverity.h>
+#include <linux/bpf.h>
 
 enum ipe_hook_type {
 	IPE_HOOK_BPRM_CHECK = 0,
@@ -18,6 +19,7 @@ enum ipe_hook_type {
 	IPE_HOOK_MPROTECT,
 	IPE_HOOK_KERNEL_READ,
 	IPE_HOOK_KERNEL_LOAD,
+	IPE_HOOK_BPF_PROG_LOAD,
 	__IPE_HOOK_MAX
 };
 
@@ -52,4 +54,17 @@ int ipe_inode_setintegrity(const struct inode *inode, enum lsm_integrity_type ty
 			   const void *value, size_t size);
 #endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
 
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+int ipe_bpf_prog_load_post_integrity(struct bpf_prog *prog,
+				     union bpf_attr *attr,
+				     struct bpf_token *token,
+				     bool kernel,
+				     const struct lsm_id *lsmid,
+				     enum lsm_integrity_verdict verdict);
+int ipe_bpf_prog_load(struct bpf_prog *prog,
+		      union bpf_attr *attr,
+		      struct bpf_token *token,
+		      bool kernel);
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
+
 #endif /* _IPE_HOOKS_H */
diff --git a/security/ipe/ipe.c b/security/ipe/ipe.c
index 495bb765de1b8..5af13903287fe 100644
--- a/security/ipe/ipe.c
+++ b/security/ipe/ipe.c
@@ -19,6 +19,9 @@ static struct lsm_blob_sizes ipe_blobs __ro_after_init = {
 #ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG
 	.lbs_inode = sizeof(struct ipe_inode),
 #endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+	.lbs_bpf_prog = sizeof(struct ipe_bpf_prog),
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
 };
 
 static const struct lsm_id ipe_lsmid = {
@@ -45,6 +48,13 @@ struct ipe_inode *ipe_inode(const struct inode *inode)
 }
 #endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
 
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+struct ipe_bpf_prog *ipe_bpf_prog(const struct bpf_prog *prog)
+{
+	return prog->aux->security + ipe_blobs.lbs_bpf_prog;
+}
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
+
 static struct security_hook_list ipe_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(bprm_check_security, ipe_bprm_check_security),
 	LSM_HOOK_INIT(bprm_creds_for_exec, ipe_bprm_creds_for_exec),
@@ -60,6 +70,10 @@ static struct security_hook_list ipe_hooks[] __ro_after_init = {
 #ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG
 	LSM_HOOK_INIT(inode_setintegrity, ipe_inode_setintegrity),
 #endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+	LSM_HOOK_INIT(bpf_prog_load_post_integrity, ipe_bpf_prog_load_post_integrity),
+	LSM_HOOK_INIT(bpf_prog_load, ipe_bpf_prog_load),
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
 };
 
 /**
diff --git a/security/ipe/ipe.h b/security/ipe/ipe.h
index 25cfdb8f0c20a..47de32b5bc938 100644
--- a/security/ipe/ipe.h
+++ b/security/ipe/ipe.h
@@ -22,6 +22,9 @@ struct ipe_bdev *ipe_bdev(struct block_device *b);
 #ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG
 struct ipe_inode *ipe_inode(const struct inode *inode);
 #endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+struct ipe_bpf_prog *ipe_bpf_prog(const struct bpf_prog *prog);
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
 
 int ipe_init_securityfs(void);
 
diff --git a/security/ipe/policy.h b/security/ipe/policy.h
index 5bfbdbddeef86..748bea92beb19 100644
--- a/security/ipe/policy.h
+++ b/security/ipe/policy.h
@@ -17,6 +17,7 @@ enum ipe_op_type {
 	IPE_OP_KEXEC_INITRAMFS,
 	IPE_OP_POLICY,
 	IPE_OP_X509,
+	IPE_OP_BPF_PROG_LOAD,
 	__IPE_OP_MAX,
 };
 
@@ -39,6 +40,19 @@ enum ipe_prop_type {
 	IPE_PROP_FSV_DIGEST,
 	IPE_PROP_FSV_SIG_FALSE,
 	IPE_PROP_FSV_SIG_TRUE,
+	IPE_PROP_BPF_SIG_NONE,
+	IPE_PROP_BPF_SIG_OK,
+	IPE_PROP_BPF_SIG_UNSIGNED,
+	IPE_PROP_BPF_SIG_PARTIALSIG,
+	IPE_PROP_BPF_SIG_UNKNOWNKEY,
+	IPE_PROP_BPF_SIG_UNEXPECTED,
+	IPE_PROP_BPF_SIG_FAULT,
+	IPE_PROP_BPF_SIG_BADSIG,
+	IPE_PROP_BPF_KEYRING_BUILTIN,
+	IPE_PROP_BPF_KEYRING_SECONDARY,
+	IPE_PROP_BPF_KEYRING_PLATFORM,
+	IPE_PROP_BPF_KERNEL_FALSE,
+	IPE_PROP_BPF_KERNEL_TRUE,
 	__IPE_PROP_MAX
 };
 
diff --git a/security/ipe/policy_parser.c b/security/ipe/policy_parser.c
index 6fa5bebf84714..71f63de56616b 100644
--- a/security/ipe/policy_parser.c
+++ b/security/ipe/policy_parser.c
@@ -237,6 +237,7 @@ static const match_table_t operation_tokens = {
 	{IPE_OP_KEXEC_INITRAMFS,	"op=KEXEC_INITRAMFS"},
 	{IPE_OP_POLICY,			"op=POLICY"},
 	{IPE_OP_X509,			"op=X509_CERT"},
+	{IPE_OP_BPF_PROG_LOAD,		"op=BPF_PROG_LOAD"},
 	{IPE_OP_INVALID,		NULL}
 };
 
@@ -281,6 +282,19 @@ static const match_table_t property_tokens = {
 	{IPE_PROP_FSV_DIGEST,		"fsverity_digest=%s"},
 	{IPE_PROP_FSV_SIG_FALSE,	"fsverity_signature=FALSE"},
 	{IPE_PROP_FSV_SIG_TRUE,		"fsverity_signature=TRUE"},
+	{IPE_PROP_BPF_SIG_NONE,		"bpf_signature=NONE"},
+	{IPE_PROP_BPF_SIG_OK,		"bpf_signature=OK"},
+	{IPE_PROP_BPF_SIG_UNSIGNED,	"bpf_signature=UNSIGNED"},
+	{IPE_PROP_BPF_SIG_PARTIALSIG,	"bpf_signature=PARTIALSIG"},
+	{IPE_PROP_BPF_SIG_UNKNOWNKEY,	"bpf_signature=UNKNOWNKEY"},
+	{IPE_PROP_BPF_SIG_UNEXPECTED,	"bpf_signature=UNEXPECTED"},
+	{IPE_PROP_BPF_SIG_FAULT,	"bpf_signature=FAULT"},
+	{IPE_PROP_BPF_SIG_BADSIG,	"bpf_signature=BADSIG"},
+	{IPE_PROP_BPF_KEYRING_BUILTIN,	"bpf_keyring=BUILTIN"},
+	{IPE_PROP_BPF_KEYRING_SECONDARY,	"bpf_keyring=SECONDARY"},
+	{IPE_PROP_BPF_KEYRING_PLATFORM,	"bpf_keyring=PLATFORM"},
+	{IPE_PROP_BPF_KERNEL_FALSE,	"bpf_kernel=FALSE"},
+	{IPE_PROP_BPF_KERNEL_TRUE,	"bpf_kernel=TRUE"},
 	{IPE_PROP_INVALID,		NULL}
 };
 
@@ -331,6 +345,19 @@ static int parse_property(char *t, struct ipe_rule *r)
 	case IPE_PROP_DMV_SIG_TRUE:
 	case IPE_PROP_FSV_SIG_FALSE:
 	case IPE_PROP_FSV_SIG_TRUE:
+	case IPE_PROP_BPF_SIG_NONE:
+	case IPE_PROP_BPF_SIG_OK:
+	case IPE_PROP_BPF_SIG_UNSIGNED:
+	case IPE_PROP_BPF_SIG_PARTIALSIG:
+	case IPE_PROP_BPF_SIG_UNKNOWNKEY:
+	case IPE_PROP_BPF_SIG_UNEXPECTED:
+	case IPE_PROP_BPF_SIG_FAULT:
+	case IPE_PROP_BPF_SIG_BADSIG:
+	case IPE_PROP_BPF_KEYRING_BUILTIN:
+	case IPE_PROP_BPF_KEYRING_SECONDARY:
+	case IPE_PROP_BPF_KEYRING_PLATFORM:
+	case IPE_PROP_BPF_KERNEL_FALSE:
+	case IPE_PROP_BPF_KERNEL_TRUE:
 		p->type = token;
 		break;
 	default:
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 08/10] hornet: Add a light skeleton data extractor scripts
From: Blaise Boscaccy @ 2026-05-07 19:14 UTC (permalink / raw)
  To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
	Serge E. Hallyn, Mickaël Salaün, Günther Noack,
	Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
	Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
	linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260507191416.2984054-1-bboscaccy@linux.microsoft.com>

These script eases light skeleton development against Hornet by
generating a data payloads which can be used for signing a light
skeleton binary using gen_sig.

Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
 scripts/hornet/extract-insn.sh | 27 +++++++++++++++++++++++++++
 scripts/hornet/extract-map.sh  | 27 +++++++++++++++++++++++++++
 scripts/hornet/extract-skel.sh | 27 +++++++++++++++++++++++++++
 3 files changed, 81 insertions(+)
 create mode 100755 scripts/hornet/extract-insn.sh
 create mode 100755 scripts/hornet/extract-map.sh
 create mode 100755 scripts/hornet/extract-skel.sh

diff --git a/scripts/hornet/extract-insn.sh b/scripts/hornet/extract-insn.sh
new file mode 100755
index 0000000000000..52338f057ff6b
--- /dev/null
+++ b/scripts/hornet/extract-insn.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (c) 2025 Microsoft Corporation
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of version 2 of the GNU General Public
+# License as published by the Free Software Foundation.
+
+function usage() {
+    echo "Sample script for extracting instructions"
+    echo "autogenerated eBPF lskel headers"
+    echo ""
+    echo "USAGE: header_file"
+    exit
+}
+
+ARGC=$#
+
+EXPECTED_ARGS=1
+
+if [ $ARGC -ne $EXPECTED_ARGS ] ; then
+    usage
+else
+    printf $(gcc -E $1 | grep "opts_insn" | \
+		 awk -F"=" '{print $2}' | sed 's/;\+$//' | sed 's/\"//g')
+fi
diff --git a/scripts/hornet/extract-map.sh b/scripts/hornet/extract-map.sh
new file mode 100755
index 0000000000000..c309f505c6238
--- /dev/null
+++ b/scripts/hornet/extract-map.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (c) 2025 Microsoft Corporation
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of version 2 of the GNU General Public
+# License as published by the Free Software Foundation.
+
+function usage() {
+    echo "Sample script for extracting instructions"
+    echo "autogenerated eBPF lskel headers"
+    echo ""
+    echo "USAGE: header_file"
+    exit
+}
+
+ARGC=$#
+
+EXPECTED_ARGS=1
+
+if [ $ARGC -ne $EXPECTED_ARGS ] ; then
+    usage
+else
+    printf $(gcc -E $1 | grep "opts_data" | \
+		 awk -F"=" '{print $2}' | sed 's/;\+$//' | sed 's/\"//g')
+fi
diff --git a/scripts/hornet/extract-skel.sh b/scripts/hornet/extract-skel.sh
new file mode 100755
index 0000000000000..6550a86b89917
--- /dev/null
+++ b/scripts/hornet/extract-skel.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (c) 2025 Microsoft Corporation
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of version 2 of the GNU General Public
+# License as published by the Free Software Foundation.
+
+function usage() {
+    echo "Sample script for extracting instructions and map data out of"
+    echo "autogenerated eBPF lskel headers"
+    echo ""
+    echo "USAGE: header_file field"
+    exit
+}
+
+ARGC=$#
+
+EXPECTED_ARGS=2
+
+if [ $ARGC -ne $EXPECTED_ARGS ] ; then
+    usage
+else
+    printf $(gcc -E $1 | grep "static const char opts_$2" | \
+		 awk -F"=" '{print $2}' | sed 's/;\+$//' | sed 's/\"//g')
+fi
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 09/10] selftests/hornet: Add a selftest for the Hornet LSM
From: Blaise Boscaccy @ 2026-05-07 19:14 UTC (permalink / raw)
  To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
	Serge E. Hallyn, Mickaël Salaün, Günther Noack,
	Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
	Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
	linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260507191416.2984054-1-bboscaccy@linux.microsoft.com>

This selftest contains a testcase that utilizes light skeleton eBPF
loaders and exercises hornet's map validation.

Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
 tools/testing/selftests/Makefile             |  1 +
 tools/testing/selftests/hornet/Makefile      | 63 ++++++++++++++++++++
 tools/testing/selftests/hornet/loader.c      | 21 +++++++
 tools/testing/selftests/hornet/trivial.bpf.c | 33 ++++++++++
 4 files changed, 118 insertions(+)
 create mode 100644 tools/testing/selftests/hornet/Makefile
 create mode 100644 tools/testing/selftests/hornet/loader.c
 create mode 100644 tools/testing/selftests/hornet/trivial.bpf.c

diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 450f13ba4cca9..4e2d1cd88c825 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -44,6 +44,7 @@ TARGETS += ftrace
 TARGETS += futex
 TARGETS += gpio
 TARGETS += hid
+TARGETS += hornet
 TARGETS += intel_pstate
 TARGETS += iommu
 TARGETS += ipc
diff --git a/tools/testing/selftests/hornet/Makefile b/tools/testing/selftests/hornet/Makefile
new file mode 100644
index 0000000000000..432bce59f54e7
--- /dev/null
+++ b/tools/testing/selftests/hornet/Makefile
@@ -0,0 +1,63 @@
+# SPDX-License-Identifier: GPL-2.0
+include ../../../build/Build.include
+include ../../../scripts/Makefile.arch
+include ../../../scripts/Makefile.include
+
+CLANG ?= clang
+CFLAGS := -g -O2 -Wall
+BPFTOOL ?= $(TOOLSDIR)/bpf/bpftool/bpftool
+SCRIPTSDIR := $(abspath ../../../../scripts/hornet)
+TOOLSDIR := $(abspath ../../..)
+LIBDIR := $(TOOLSDIR)/lib
+BPFDIR := $(LIBDIR)/bpf
+TOOLSINCDIR := $(TOOLSDIR)/include
+APIDIR := $(TOOLSINCDIR)/uapi
+CERTDIR := $(abspath ../../../../certs)
+PKG_CONFIG ?= $(CROSS_COMPILE)pkg-config
+
+TEST_GEN_PROGS := loader
+TEST_GEN_FILES := vmlinux.h loader.h trivial.bpf.o map.bin sig.bin insn.bin signed_loader.h
+$(TEST_GEN_PROGS): LDLIBS += -lbpf
+$(TEST_GEN_PROGS): $(TEST_GEN_FILES)
+
+include ../lib.mk
+
+BPF_CFLAGS := -target bpf \
+	-D__TARGET_ARCH_$(ARCH) \
+	-I/usr/include/$(shell uname -m)-linux-gnu \
+	$(KHDR_INCLUDES)
+
+vmlinux.h:
+	$(BPFTOOL) btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
+
+trivial.bpf.o: trivial.bpf.c vmlinux.h
+	$(CLANG) $(CFLAGS) $(BPF_CFLAGS) -c $< -o $@
+
+loader.h: trivial.bpf.o
+	$(BPFTOOL) gen skeleton -S -k $(CERTDIR)/signing_key.pem -i $(CERTDIR)/signing_key.x509 \
+		-L $< name trivial > $@
+
+insn.bin: loader.h
+	$(SCRIPTSDIR)/extract-insn.sh $< > $@
+
+map.bin: loader.h
+	$(SCRIPTSDIR)/extract-map.sh $< > $@
+
+$(OUTPUT)/gen_sig: ../../../../scripts/hornet/gen_sig.c
+	$(call msg,GEN_SIG,,$@)
+	$(Q)$(CC) $(shell $(PKG_CONFIG) --cflags libcrypto 2> /dev/null) \
+		  $< -o $@ \
+		  $(shell $(PKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto)
+
+sig.bin: insn.bin map.bin $(OUTPUT)/gen_sig
+	$(OUTPUT)/gen_sig --key $(CERTDIR)/signing_key.pem --cert $(CERTDIR)/signing_key.x509 \
+		--data insn.bin --add map.bin:0 --out sig.bin
+
+signed_loader.h: sig.bin
+	$(SCRIPTSDIR)/write-sig.sh loader.h sig.bin > $@
+
+loader: loader.c signed_loader.h
+	$(CC) $(CFLAGS) -I$(LIBDIR) -I$(APIDIR) $< -o $@ -lbpf
+
+
+EXTRA_CLEAN = $(OUTPUT)/gen_sig
diff --git a/tools/testing/selftests/hornet/loader.c b/tools/testing/selftests/hornet/loader.c
new file mode 100644
index 0000000000000..f27580c7262b3
--- /dev/null
+++ b/tools/testing/selftests/hornet/loader.c
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stddef.h>
+#include <sys/resource.h>
+#include <bpf/libbpf.h>
+#include <errno.h>
+#include  "signed_loader.h"
+
+int main(int argc, char **argv)
+{
+	struct trivial *skel;
+
+	skel = trivial__open_and_load();
+	if (!skel)
+		return -1;
+
+	trivial__destroy(skel);
+	return 0;
+}
diff --git a/tools/testing/selftests/hornet/trivial.bpf.c b/tools/testing/selftests/hornet/trivial.bpf.c
new file mode 100644
index 0000000000000..d38c5b53ff932
--- /dev/null
+++ b/tools/testing/selftests/hornet/trivial.bpf.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+#include "vmlinux.h"
+
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_core_read.h>
+
+char LICENSE[] SEC("license") = "Dual BSD/GPL";
+
+int monitored_pid = 0;
+
+SEC("tracepoint/syscalls/sys_enter_unlinkat")
+int handle_enter_unlink(struct trace_event_raw_sys_enter *ctx)
+{
+	char filename[128] = { 0 };
+	struct task_struct *task;
+	unsigned long start_time = 0;
+	int pid = bpf_get_current_pid_tgid() >> 32;
+	char *pathname_ptr = (char *) BPF_CORE_READ(ctx, args[1]);
+
+	bpf_probe_read_str(filename, sizeof(filename), pathname_ptr);
+	task = (struct task_struct *)bpf_get_current_task();
+	start_time = BPF_CORE_READ(task, start_time);
+
+	bpf_printk("BPF triggered unlinkat by PID: %d, start_time %ld. pathname = %s",
+		   pid, start_time, filename);
+
+	if (monitored_pid == pid)
+		bpf_printk("target pid found");
+
+	return 0;
+}
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 07/10] hornet: Introduce gen_sig
From: Blaise Boscaccy @ 2026-05-07 19:14 UTC (permalink / raw)
  To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
	Serge E. Hallyn, Mickaël Salaün, Günther Noack,
	Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
	Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
	linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260507191416.2984054-1-bboscaccy@linux.microsoft.com>

This introduces the gen_sig tool. It creates a pkcs#7 signature of a
data payload. Additionally it appends a signed attribute containing a
set of hashes.

Typical usage is to provide a payload containing the light skeleton
ebpf syscall program binary and it's associated maps, which can be
extracted from the auto-generated skeleton header.

Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
 scripts/Makefile            |   1 +
 scripts/hornet/Makefile     |   5 +
 scripts/hornet/gen_sig.c    | 401 ++++++++++++++++++++++++++++++++++++
 scripts/hornet/write-sig.sh |  27 +++
 4 files changed, 434 insertions(+)
 create mode 100644 scripts/hornet/Makefile
 create mode 100644 scripts/hornet/gen_sig.c
 create mode 100755 scripts/hornet/write-sig.sh

diff --git a/scripts/Makefile b/scripts/Makefile
index 0941e5ce7b575..dea8ab91bbe4e 100644
--- a/scripts/Makefile
+++ b/scripts/Makefile
@@ -63,6 +63,7 @@ subdir-$(CONFIG_GENKSYMS) += genksyms
 subdir-$(CONFIG_GENDWARFKSYMS) += gendwarfksyms
 subdir-$(CONFIG_SECURITY_SELINUX) += selinux
 subdir-$(CONFIG_SECURITY_IPE) += ipe
+subdir-$(CONFIG_SECURITY_HORNET) += hornet
 
 # Let clean descend into subdirs
 subdir-	+= basic dtc gdb kconfig mod
diff --git a/scripts/hornet/Makefile b/scripts/hornet/Makefile
new file mode 100644
index 0000000000000..3ee41e5e9a9ff
--- /dev/null
+++ b/scripts/hornet/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+hostprogs-always-y	:= gen_sig
+
+HOSTCFLAGS_gen_sig.o = $(shell $(HOSTPKG_CONFIG) --cflags libcrypto 2> /dev/null)
+HOSTLDLIBS_gen_sig = $(shell $(HOSTPKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto)
diff --git a/scripts/hornet/gen_sig.c b/scripts/hornet/gen_sig.c
new file mode 100644
index 0000000000000..4d59b70f5fff4
--- /dev/null
+++ b/scripts/hornet/gen_sig.c
@@ -0,0 +1,401 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+ *
+ * Generate a signature for an eBPF program along with appending
+ * map hashes as signed attributes
+ *
+ * Copyright © 2025      Microsoft Corporation.
+ *
+ * Authors: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the licence, or (at your option) any later version.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <err.h>
+#include <getopt.h>
+
+#include <openssl/cms.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/pkcs7.h>
+#include <openssl/x509.h>
+#include <openssl/pem.h>
+#include <openssl/objects.h>
+#include <openssl/asn1.h>
+#include <openssl/asn1t.h>
+#include <openssl/opensslv.h>
+#include <openssl/bio.h>
+#include <openssl/stack.h>
+
+#if OPENSSL_VERSION_MAJOR >= 3
+# define USE_PKCS11_PROVIDER
+# include <openssl/provider.h>
+# include <openssl/store.h>
+#else
+# if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0)
+#  define USE_PKCS11_ENGINE
+#  include <openssl/engine.h>
+# endif
+#endif
+#include "../ssl-common.h"
+
+#define SHA256_LEN 32
+#define BUF_SIZE   (1 << 15) // 32 KiB
+#define MAX_HASHES 64
+
+struct hash_spec {
+	char *file;
+	int index;
+};
+
+typedef struct {
+	ASN1_INTEGER *index;
+	ASN1_OCTET_STRING *hash;
+
+} HORNET_MAP;
+
+DECLARE_ASN1_FUNCTIONS(HORNET_MAP)
+ASN1_SEQUENCE(HORNET_MAP) = {
+	ASN1_SIMPLE(HORNET_MAP, index, ASN1_INTEGER),
+	ASN1_SIMPLE(HORNET_MAP, hash, ASN1_OCTET_STRING)
+} ASN1_SEQUENCE_END(HORNET_MAP);
+
+IMPLEMENT_ASN1_FUNCTIONS(HORNET_MAP)
+
+DEFINE_STACK_OF(HORNET_MAP)
+
+typedef struct {
+	STACK_OF(HORNET_MAP) * maps;
+} MAP_SET;
+
+DECLARE_ASN1_FUNCTIONS(MAP_SET)
+ASN1_SEQUENCE(MAP_SET) = {
+	ASN1_SET_OF(MAP_SET, maps, HORNET_MAP)
+} ASN1_SEQUENCE_END(MAP_SET);
+
+IMPLEMENT_ASN1_FUNCTIONS(MAP_SET)
+
+#define DIE(...) do { fprintf(stderr, __VA_ARGS__); fputc('\n', stderr); \
+		exit(EXIT_FAILURE); } while (0)
+
+static BIO *bio_open_wr(const char *path)
+{
+	BIO *b = BIO_new_file(path, "wb");
+
+	if (!b) {
+		perror(path);
+		ERR_print_errors_fp(stderr);
+		exit(EXIT_FAILURE);
+	}
+	return b;
+}
+
+static void usage(const char *prog)
+{
+	fprintf(stderr,
+		"Usage:\n"
+		"  %s --data content.bin --cert signer.crt --key signer.key [-pass pass]\n"
+		"     --out newsig.p7b \n"
+		"     --add FILE:index [--add FILE:index ...]\n",
+		prog);
+}
+
+static const char *key_pass;
+
+static int pem_pw_cb(char *buf, int len, int w, void *v)
+{
+	int pwlen;
+
+	if (!key_pass)
+		return -1;
+
+	pwlen = strlen(key_pass);
+	if (pwlen >= len)
+		return -1;
+
+	strcpy(buf, key_pass);
+
+	key_pass = NULL;
+
+	return pwlen;
+}
+
+static EVP_PKEY *read_private_key(const char *private_key_name)
+{
+	EVP_PKEY *private_key;
+	BIO *b;
+
+	b = BIO_new_file(private_key_name, "rb");
+	ERR(!b, "%s", private_key_name);
+	private_key = PEM_read_bio_PrivateKey(b, NULL, pem_pw_cb,
+					      NULL);
+	ERR(!private_key, "%s", private_key_name);
+	BIO_free(b);
+
+	return private_key;
+}
+
+static X509 *read_x509(const char *x509_name)
+{
+	unsigned char buf[2];
+	X509 *x509;
+	BIO *b;
+	int n;
+
+	b = BIO_new_file(x509_name, "rb");
+	ERR(!b, "%s", x509_name);
+
+	/* Look at the first two bytes of the file to determine the encoding */
+	n = BIO_read(b, buf, 2);
+	if (n != 2) {
+		if (BIO_should_retry(b)) {
+			fprintf(stderr, "%s: Read wanted retry\n", x509_name);
+			exit(1);
+		}
+		if (n >= 0) {
+			fprintf(stderr, "%s: Short read\n", x509_name);
+			exit(1);
+		}
+		ERR(1, "%s", x509_name);
+	}
+
+	ERR(BIO_reset(b) != 0, "%s", x509_name);
+
+	if (buf[0] == 0x30 && buf[1] >= 0x81 && buf[1] <= 0x84)
+		/* Assume raw DER encoded X.509 */
+		x509 = d2i_X509_bio(b, NULL);
+	else
+		/* Assume PEM encoded X.509 */
+		x509 = PEM_read_bio_X509(b, NULL, NULL, NULL);
+
+	BIO_free(b);
+	ERR(!x509, "%s", x509_name);
+
+	return x509;
+}
+
+static int sha256(const char *path, unsigned char out[SHA256_LEN], unsigned int *out_len)
+{
+	FILE *f;
+	int rc;
+	EVP_MD_CTX *ctx;
+	unsigned char buf[BUF_SIZE];
+	size_t n;
+	unsigned int mdlen = 0;
+
+	if (!path || !out)
+		return -1;
+
+	f = fopen(path, "rb");
+	if (!f) {
+		perror("fopen");
+		return -2;
+	}
+
+	ERR_load_crypto_strings();
+
+	rc = -3;
+	ctx = EVP_MD_CTX_new();
+	if (!ctx) {
+		rc = -4;
+		goto done;
+	}
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+	if (EVP_DigestInit_ex2(ctx, EVP_sha256(), NULL) != 1) {
+		rc = -5;
+		goto done;
+	}
+#else
+	if (EVP_DigestInit_ex(ctx, EVP_sha256(), NULL) != 1) {
+		rc = -5;
+		goto done;
+	}
+#endif
+	while ((n = fread(buf, 1, sizeof(buf), f)) > 0) {
+		if (EVP_DigestUpdate(ctx, buf, n) != 1) {
+			rc = -6;
+			goto done;
+		}
+	}
+	if (ferror(f)) {
+		rc = -7;
+		goto done;
+	}
+
+	if (EVP_DigestFinal_ex(ctx, out, &mdlen) != 1) {
+		rc = -8;
+		goto done;
+	}
+	if (mdlen != SHA256_LEN) {
+		rc = -9;
+		goto done;
+	}
+
+	if (out_len)
+		*out_len = mdlen;
+	rc = 0;
+
+done:
+	EVP_MD_CTX_free(ctx);
+	fclose(f);
+	ERR_free_strings();
+	return rc;
+}
+
+static void add_hash(MAP_SET *set, unsigned char *buffer, int buffer_len, int index)
+{
+	HORNET_MAP *map = NULL;
+
+	map = HORNET_MAP_new();
+	ASN1_INTEGER_set(map->index, index);
+	ASN1_OCTET_STRING_set(map->hash, buffer, buffer_len);
+	sk_HORNET_MAP_push(set->maps, map);
+}
+
+int main(int argc, char **argv)
+{
+	const char *cert_path = NULL;
+	const char *key_path = NULL;
+	const char *data_path = NULL;
+	const char *out_path = NULL;
+
+	X509 *signer;
+	EVP_PKEY *pkey;
+	BIO *data_in;
+	CMS_ContentInfo *cms_out;
+	struct hash_spec hashes[MAX_HASHES];
+	int hash_count = 0;
+	int flags;
+	CMS_SignerInfo *si;
+	MAP_SET *set;
+	unsigned char hash_buffer[SHA256_LEN];
+	unsigned int hash_len;
+	ASN1_OBJECT *oid;
+	unsigned char *der = NULL;
+	int der_len;
+	int err;
+	BIO *b_out;
+	int i;
+	int opt;
+
+	const char *short_opts = "C:K:P:O:A:Sh";
+
+	static const struct option long_opts[] = {
+		{"cert", required_argument, 0, 'C'},
+		{"key",  required_argument, 0, 'K'},
+		{"pass",  required_argument, 0, 'P'},
+		{"out",  required_argument, 0, 'O'},
+		{"data",  required_argument, 0, 'D'},
+		{"add",  required_argument, 0, 'A'},
+		{"help",    no_argument,       0, 'h'},
+		{0, 0, 0, 0}
+	};
+
+	while ((opt = getopt_long_only(argc, argv, short_opts, long_opts, NULL)) != -1) {
+		switch (opt) {
+		case 'C':
+			cert_path = optarg;
+			break;
+		case 'K':
+			key_path = optarg;
+			break;
+		case 'P':
+			key_pass = optarg;
+			break;
+		case 'O':
+			out_path = optarg;
+			break;
+		case 'D':
+			data_path = optarg;
+			break;
+		case 'A':
+			if (strchr(optarg, ':')) {
+				hashes[hash_count].file = strsep(&optarg, ":");
+				hashes[hash_count].index = atoi(optarg);
+				if (++hash_count >= MAX_HASHES) {
+					usage(argv[0]);
+					return EXIT_FAILURE;
+				}
+			} else {
+				usage(argv[0]);
+				return EXIT_FAILURE;
+			}
+			break;
+		default:
+			usage(argv[0]);
+			return EXIT_FAILURE;
+		}
+	}
+
+	if (!cert_path || !key_path || !out_path || !data_path) {
+		usage(argv[0]);
+		return EXIT_FAILURE;
+	}
+
+	OpenSSL_add_all_algorithms();
+	ERR_load_crypto_strings();
+
+	signer = read_x509(cert_path);
+	ERR(!signer, "Load cert failed");
+
+	pkey = read_private_key(key_path);
+	ERR(!pkey, "Load key failed");
+
+	data_in = BIO_new_file(data_path, "rb");
+	ERR(!data_in, "Load data failed");
+
+	cms_out = CMS_sign(NULL, NULL, NULL, NULL,
+			   CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY | CMS_DETACHED);
+	ERR(!cms_out, "create cms failed");
+
+	flags = CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY | CMS_NOSMIMECAP | CMS_DETACHED;
+
+	si = CMS_add1_signer(cms_out, signer, pkey, EVP_sha256(), flags);
+	ERR(!si, "add signer failed");
+
+	set = MAP_SET_new();
+	set->maps = sk_HORNET_MAP_new_null();
+
+	for (i = 0; i < hash_count; i++) {
+		if (sha256(hashes[i].file, hash_buffer, &hash_len) != 0) {
+			DIE("failed to hash input");
+		}
+		add_hash(set, hash_buffer, hash_len, hashes[i].index);
+	}
+
+	oid = OBJ_txt2obj("2.25.316487325684022475439036912669789383960", 1);
+	if (!oid) {
+		ERR_print_errors_fp(stderr);
+		DIE("create oid failed");
+	}
+
+	der_len = ASN1_item_i2d((ASN1_VALUE *)set, &der, ASN1_ITEM_rptr(MAP_SET));
+	CMS_signed_add1_attr_by_OBJ(si, oid, V_ASN1_SEQUENCE, der, der_len);
+
+	err = CMS_final(cms_out, data_in, NULL, CMS_NOCERTS | CMS_BINARY);
+	ERR(!err, "cms final failed");
+
+	OPENSSL_free(der);
+	MAP_SET_free(set);
+
+	b_out = bio_open_wr(out_path);
+	ERR(!b_out, "opening output path failed");
+
+	i2d_CMS_bio_stream(b_out, cms_out, NULL, 0);
+
+	BIO_free(data_in);
+	BIO_free(b_out);
+	EVP_cleanup();
+	ERR_free_strings();
+	return 0;
+}
diff --git a/scripts/hornet/write-sig.sh b/scripts/hornet/write-sig.sh
new file mode 100755
index 0000000000000..7eaabe3bab9aa
--- /dev/null
+++ b/scripts/hornet/write-sig.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (c) 2025 Microsoft Corporation
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of version 2 of the GNU General Public
+# License as published by the Free Software Foundation.
+
+function usage() {
+    echo "Sample for rewriting an autogenerated eBPF lskel headers"
+    echo "with a new signature"
+    echo ""
+    echo "USAGE: header_file sig"
+    exit
+}
+
+ARGC=$#
+
+EXPECTED_ARGS=2
+
+if [ $ARGC -ne $EXPECTED_ARGS ] ; then
+    usage
+else
+    SIG=$(xxd -p $2 | tr -d '\n' | sed 's/\(..\)/\\\\x\1/g')
+    sed '/const char opts_sig/,/;/c\\tstatic const char opts_sig[] __attribute__((__aligned__(8))) = "\\\n'"$(printf '%s\n' "$SIG")"'\";' $1
+fi
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 06/10] security: Hornet LSM
From: Blaise Boscaccy @ 2026-05-07 19:14 UTC (permalink / raw)
  To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
	Serge E. Hallyn, Mickaël Salaün, Günther Noack,
	Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
	Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
	linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260507191416.2984054-1-bboscaccy@linux.microsoft.com>

This adds the Hornet Linux Security Module which provides enhanced
signature verification and data validation for eBPF programs. This
allows users to continue to maintain an invariant that all code
running inside of the kernel has actually been signed and verified, by
the kernel.

This effort builds upon the currently excepted upstream solution. It
further hardens it by providing deterministic, in-kernel checking of
map hashes to solidify auditing along with preventing TOCTOU attacks
against lskel map hashes.

Target map hashes are passed in via PKCS#7 signed attributes. Hornet
determines the extent which the eBFP program is signed and defers to
other LSMs for policy decisions.

Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
Nacked-by: Alexei Starovoitov <alexei.starovoitov@gmail.com>
---
 Documentation/admin-guide/LSM/Hornet.rst | 323 +++++++++++++++++++++
 Documentation/admin-guide/LSM/index.rst  |   1 +
 MAINTAINERS                              |   9 +
 include/linux/oid_registry.h             |   3 +
 include/uapi/linux/lsm.h                 |   1 +
 security/Kconfig                         |   3 +-
 security/Makefile                        |   1 +
 security/hornet/Kconfig                  |  13 +
 security/hornet/Makefile                 |   7 +
 security/hornet/hornet.asn1              |  12 +
 security/hornet/hornet_lsm.c             | 352 +++++++++++++++++++++++
 11 files changed, 724 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/admin-guide/LSM/Hornet.rst
 create mode 100644 security/hornet/Kconfig
 create mode 100644 security/hornet/Makefile
 create mode 100644 security/hornet/hornet.asn1
 create mode 100644 security/hornet/hornet_lsm.c

diff --git a/Documentation/admin-guide/LSM/Hornet.rst b/Documentation/admin-guide/LSM/Hornet.rst
new file mode 100644
index 0000000000000..0ade4c17374c6
--- /dev/null
+++ b/Documentation/admin-guide/LSM/Hornet.rst
@@ -0,0 +1,323 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+======
+Hornet
+======
+
+Hornet is a Linux Security Module that provides extensible signature
+verification for eBPF programs. This is selectable at build-time with
+``CONFIG_SECURITY_HORNET``.
+
+Overview
+========
+
+Hornet addresses concerns from users who require strict audit trails and
+verification guarantees for eBPF programs, especially in
+security-sensitive environments. Many production systems need assurance
+that only authorized, unmodified eBPF programs are loaded into the
+kernel. Hornet provides this assurance through cryptographic signature
+verification.
+
+When an eBPF program is loaded via the ``bpf()`` syscall, Hornet
+verifies a PKCS#7 signature attached to the program instructions. The
+signature is checked against whichever keyring was specified by the user
+existing kernel cryptographic infrastructure. In addition to signing the
+program bytecode, Hornet supports signing SHA-256 hashes of associated
+BPF maps, enabling integrity verification of map contents at load time
+and at runtime.
+
+After verification, Hornet classifies the program into one of the
+following integrity states and passes the result to a downstream LSM hook
+(``bpf_prog_load_post_integrity``), allowing other security modules to
+make policy decisions based on the verification outcome:
+
+``LSM_INT_VERDICT_OK``
+  The program signature and all map hashes verified successfully.
+
+``LSM_INT_VERDICT_UNSIGNED``
+  No signature was provided with the program.
+
+``LSM_INT_VERDICT_PARTIALSIG``
+  The program signature verified, but the signature did not contain
+  hornet map hash data.
+
+``LSM_INT_VERDICT_UNKNOWNKEY``
+  The keyring requested by the user is invalid.
+
+``LSM_INT_VERDICT_FAULT``
+  A system error occurred during verification.
+
+``LSM_INT_VERDICT_UNEXPECTED``
+  An unexpected map hash value was encountered.
+
+``LSM_INT_VERDICT_BADSIG``
+  The signature or a map hash failed verification.
+
+Hornet itself does not enforce a policy on whether unsigned or partially
+signed programs should be rejected. It delegates that decision to
+downstream LSMs via the ``bpf_prog_load_post_integrity`` hook, making it
+a composable building block in a larger security architecture.
+
+Use Cases
+=========
+
+- **Locked-down production environments**: Ensure only eBPF programs
+  signed by a trusted authority can be loaded, preventing unauthorized
+  or tampered programs from running in the kernel.
+
+- **Audit and compliance**: Provide cryptographic evidence that loaded
+  eBPF programs match their expected build artifacts, supporting
+  compliance requirements in regulated industries.
+
+- **Supply chain integrity**: Verify that eBPF programs and their
+  associated map data have not been modified since they were built and
+  signed, protecting against supply chain attacks.
+
+Threat Model
+============
+
+Hornet protects against the following threats:
+
+- **Unauthorized eBPF program loading**: Programs that have not been
+  signed by a trusted key will be reported as unsigned or badly signed.
+
+- **Tampering with program instructions**: Any modification to the eBPF
+  bytecode after signing will cause signature verification to fail.
+
+- **Tampering with map data**: When map hashes are included in the
+  signature, Hornet verifies that frozen BPF maps match their expected
+  SHA-256 hashes at load time. Maps are also re-verified before program
+  execution via ``BPF_PROG_RUN``.
+
+Hornet does **not** protect against:
+
+- Compromise of the signing key itself.
+- Attacks that occur after a program has been loaded and verified.
+- Programs loaded by the kernel itself (kernel-internal loads bypass
+  the ``BPF_PROG_RUN`` map check).
+
+Known Limitations
+=================
+
+- Hornet requires programs to use :doc:`light skeletons
+  </bpf/libbpf/libbpf_naming_convention>` (lskels) for the signing
+  workflow, as the tooling operates on lskel-generated headers.
+
+- A maximum of 64 maps per program can be tracked for hash
+  verification.
+
+- Map hash verification requires the maps to be frozen before loading.
+  Maps that are not frozen at load time will cause verification to fail
+  when their hashes are included in the signature.
+
+- The only hashing algorithm available is SHA256 due to it be hardcoded
+  in the bpf subsystem.
+
+- Hornet guarantees that the signed program runs only with signed map
+  data. It does not guarantee positional binding of maps to specific
+  fd_array slots.
+
+- BPF_MAP_TYPE_PROG_ARRAY maps must be frozen for Hornet to verify
+  them. Unfrozen prog array maps are not covered by verification.
+
+Configuration
+=============
+
+Build Configuration
+-------------------
+
+Enable Hornet by setting the following kernel configuration option::
+
+  CONFIG_SECURITY_HORNET=y
+
+This option is found under :menuselection:`Security options --> Hornet
+support` and depends on ``CONFIG_SECURITY``.
+
+When enabled, Hornet is included in the default LSM initialization order
+and will appear in ``/sys/kernel/security/lsm``.
+
+Architecture
+============
+
+Signature Verification Flow
+---------------------------
+
+The following describes what happens when a userspace program calls
+``bpf(BPF_PROG_LOAD, ...)`` with a signature attached:
+
+1. The ``bpf_prog_load_integrity`` LSM hook is invoked.
+
+2. Hornet reads the signature from the userspace buffer specified by
+   ``attr->signature`` (with length ``attr->signature_size``).
+
+3. The PKCS#7 signature is verified against the program instructions
+   using ``verify_pkcs7_message_sig()`` with the user specified keyring.
+
+4. The PKCS#7 message is parsed and its trust chain is validated via
+   ``validate_pkcs7_trust()``.
+
+5. Hornet extracts the authenticated attribute identified by
+   ``OID_hornet_data`` (OID ``2.25.316487325684022475439036912669789383960``)
+   from the PKCS#7 message. This attribute contains an ASN.1-encoded set
+   of map index/hash pairs.
+
+6. For each map hash entry, Hornet retrieves the corresponding BPF map
+   via its file descriptor, confirms it is frozen, computes its SHA-256
+   hash, and compares it against the signed hash.
+
+7. The resulting integrity verdict is passed to the
+   ``bpf_prog_load_post_integrity`` hook so that downstream LSMs can
+   enforce policy.
+
+Runtime Map Verification
+------------------------
+
+When ``bpf(BPF_PROG_RUN, ...)`` is called from userspace, Hornet
+re-verifies the hashes of all maps associated with the program. This
+ensures that map contents have not been modified between program load
+and execution. If any map hash no longer matches, the ``BPF_PROG_RUN``
+command is denied.
+
+Userspace Interface
+-------------------
+
+Signatures are passed to the kernel through fields in ``union bpf_attr``
+when using the ``BPF_PROG_LOAD`` command:
+
+``signature``
+  A pointer to a userspace buffer containing the PKCS#7 signature.
+
+``signature_size``
+  The size of the signature buffer in bytes.
+
+ASN.1 Schema
+------------
+
+Map hashes are encoded as a signed attribute in the PKCS#7 message using
+the following ASN.1 schema::
+
+  HornetData ::= SET OF Map
+
+  Map ::= SEQUENCE {
+      index   INTEGER,
+      sha     OCTET STRING
+  }
+
+Each ``Map`` entry contains the index of the map in the program's
+``fd_array`` and its expected SHA-256 hash. A zero-length ``sha`` field
+indicates that the map at that index should be skipped during
+verification.
+
+Tooling
+=======
+
+Helper scripts and a signature generation tool are provided in
+``scripts/hornet/`` to support the development of signed eBPF light
+skeletons.
+
+gen_sig
+-------
+
+``gen_sig`` is a C program (using OpenSSL) that creates a PKCS#7
+signature over eBPF program instructions and optionally includes
+SHA-256 hashes of BPF maps as signed attributes.
+
+Usage::
+
+  gen_sig --data <instructions.bin> \
+          --cert <signer.crt> \
+          --key <signer.key> \
+          [--pass <passphrase>] \
+          --out <signature.p7b> \
+          [--add <mapfile.bin>:<index> ...]
+
+``--data``
+  Path to the binary file containing eBPF program instructions to sign.
+
+``--cert``
+  Path to the signing certificate (PEM or DER format).
+
+``--key``
+  Path to the private key (PEM or DER format).
+
+``--pass``
+  Optional passphrase for the private key.
+
+``--out``
+  Path to write the output PKCS#7 signature.
+
+``--add``
+  Attach a map hash as a signed attribute. The argument is a path to a
+  binary map file followed by a colon and the map's index in the
+  ``fd_array``. This option may be specified multiple times.
+
+extract-skel.sh
+---------------
+
+Extracts a named field from an autogenerated eBPF lskel header file.
+Used internally by other helper scripts.
+
+extract-insn.sh
+---------------
+
+Extracts the eBPF program instructions (``opts_insn``) from an lskel
+header into a binary file suitable for signing with ``gen_sig``.
+
+extract-map.sh
+--------------
+
+Extracts the map data (``opts_data``) from an lskel header into a
+binary file suitable for hashing with ``gen_sig``.
+
+write-sig.sh
+------------
+
+Replaces the signature data in an lskel header with a new signature
+from a binary file. This is used to embed a freshly generated signature
+back into the header after signing.
+
+Signing Workflow
+================
+
+A typical workflow for building and signing an eBPF light skeleton is:
+
+1. **Compile the eBPF program**::
+
+     clang -O2 -target bpf -c program.bpf.c -o program.bpf.o
+
+2. **Generate the light skeleton header** using ``bpftool``::
+
+     bpftool gen skeleton -S program.bpf.o > loader.h
+
+3. **Extract instructions and map data** from the generated header::
+
+     scripts/hornet/extract-insn.sh loader.h > insn.bin
+     scripts/hornet/extract-map.sh loader.h > map.bin
+
+4. **Generate the signature** with ``gen_sig``::
+
+     scripts/hornet/gen_sig \
+       --key signing_key.pem \
+       --cert signing_key.x509 \
+       --data insn.bin \
+       --add map.bin:0 \
+       --out sig.bin
+
+5. **Embed the signature** back into the header::
+
+     scripts/hornet/write-sig.sh loader.h sig.bin > signed_loader.h
+
+6. **Build the loader program** using the signed header::
+
+     cc -o loader loader.c -lbpf
+
+The resulting loader program will pass the embedded signature to the
+kernel when loading the eBPF program, enabling Hornet to verify it.
+
+Testing
+=======
+
+Self-tests are provided in ``tools/testing/selftests/hornet/``. The test
+suite builds a minimal eBPF program (``trivial.bpf.c``), signs it using
+the workflow described above, and verifies that the signed program loads
+successfully.
diff --git a/Documentation/admin-guide/LSM/index.rst b/Documentation/admin-guide/LSM/index.rst
index b44ef68f6e4da..57f6e9fbe5fd1 100644
--- a/Documentation/admin-guide/LSM/index.rst
+++ b/Documentation/admin-guide/LSM/index.rst
@@ -49,3 +49,4 @@ subdirectories.
    SafeSetID
    ipe
    landlock
+   Hornet
diff --git a/MAINTAINERS b/MAINTAINERS
index d1cc0e12fe1f0..0942f5453c04d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11692,6 +11692,15 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/iio/pressure/honeywell,mprls0025pa.yaml
 F:	drivers/iio/pressure/mprls0025pa*
 
+HORNET SECURITY MODULE
+M:	Blaise Boscaccy <bboscaccy@linux.microsoft.com>
+L:	linux-security-module@vger.kernel.org
+S:	Supported
+T:	git https://github.com/blaiseboscaccy/hornet.git
+F:	Documentation/admin-guide/LSM/Hornet.rst
+F:	scripts/hornet/
+F:	security/hornet/
+
 HP BIOSCFG DRIVER
 M:	Jorge Lopez <jorge.lopez2@hp.com>
 L:	platform-driver-x86@vger.kernel.org
diff --git a/include/linux/oid_registry.h b/include/linux/oid_registry.h
index ebce402854de4..bf852715aaea4 100644
--- a/include/linux/oid_registry.h
+++ b/include/linux/oid_registry.h
@@ -150,6 +150,9 @@ enum OID {
 	OID_id_ml_dsa_65,			/* 2.16.840.1.101.3.4.3.18 */
 	OID_id_ml_dsa_87,			/* 2.16.840.1.101.3.4.3.19 */
 
+	/* Hornet LSM */
+	OID_hornet_data,	  /* 2.25.316487325684022475439036912669789383960 */
+
 	OID__NR
 };
 
diff --git a/include/uapi/linux/lsm.h b/include/uapi/linux/lsm.h
index 938593dfd5daf..2ff9bcdd551e2 100644
--- a/include/uapi/linux/lsm.h
+++ b/include/uapi/linux/lsm.h
@@ -65,6 +65,7 @@ struct lsm_ctx {
 #define LSM_ID_IMA		111
 #define LSM_ID_EVM		112
 #define LSM_ID_IPE		113
+#define LSM_ID_HORNET		114
 
 /*
  * LSM_ATTR_XXX definitions identify different LSM attributes
diff --git a/security/Kconfig b/security/Kconfig
index 6a4393fce9a17..283c4a1032094 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -230,6 +230,7 @@ source "security/safesetid/Kconfig"
 source "security/lockdown/Kconfig"
 source "security/landlock/Kconfig"
 source "security/ipe/Kconfig"
+source "security/hornet/Kconfig"
 
 source "security/integrity/Kconfig"
 
@@ -274,7 +275,7 @@ config LSM
 	default "landlock,lockdown,yama,loadpin,safesetid,apparmor,selinux,smack,tomoyo,ipe,bpf" if DEFAULT_SECURITY_APPARMOR
 	default "landlock,lockdown,yama,loadpin,safesetid,tomoyo,ipe,bpf" if DEFAULT_SECURITY_TOMOYO
 	default "landlock,lockdown,yama,loadpin,safesetid,ipe,bpf" if DEFAULT_SECURITY_DAC
-	default "landlock,lockdown,yama,loadpin,safesetid,selinux,smack,tomoyo,apparmor,ipe,bpf"
+	default "landlock,lockdown,yama,loadpin,safesetid,selinux,smack,tomoyo,apparmor,ipe,hornet,bpf"
 	help
 	  A comma-separated list of LSMs, in initialization order.
 	  Any LSMs left off this list, except for those with order
diff --git a/security/Makefile b/security/Makefile
index 4601230ba442a..b68cb56e419bc 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_CGROUPS)			+= device_cgroup.o
 obj-$(CONFIG_BPF_LSM)			+= bpf/
 obj-$(CONFIG_SECURITY_LANDLOCK)		+= landlock/
 obj-$(CONFIG_SECURITY_IPE)		+= ipe/
+obj-$(CONFIG_SECURITY_HORNET)		+= hornet/
 
 # Object integrity file lists
 obj-$(CONFIG_INTEGRITY)			+= integrity/
diff --git a/security/hornet/Kconfig b/security/hornet/Kconfig
new file mode 100644
index 0000000000000..5be71d97daee2
--- /dev/null
+++ b/security/hornet/Kconfig
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config SECURITY_HORNET
+	bool "Hornet support"
+	select CRYPTO_LIB_SHA256
+	select PKCS7_MESSAGE_PARSER
+	select SYSTEM_DATA_VERIFICATION
+	default n
+	help
+	  This selects Hornet.
+	  Further information can be found in
+	  Documentation/admin-guide/LSM/Hornet.rst.
+
+	  If you are unsure how to answer this question, answer N.
diff --git a/security/hornet/Makefile b/security/hornet/Makefile
new file mode 100644
index 0000000000000..26b6f954f762e
--- /dev/null
+++ b/security/hornet/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_SECURITY_HORNET) := hornet.o
+
+hornet-y := hornet.asn1.o \
+	hornet_lsm.o \
+
+$(obj)/hornet.asn1.o: $(obj)/hornet.asn1.c $(obj)/hornet.asn1.h
diff --git a/security/hornet/hornet.asn1 b/security/hornet/hornet.asn1
new file mode 100644
index 0000000000000..e60abf451ae23
--- /dev/null
+++ b/security/hornet/hornet.asn1
@@ -0,0 +1,12 @@
+-- SPDX-License-Identifier: BSD-3-Clause
+--
+-- Copyright (C) 2026 Microsoft
+--
+-- https://www.rfc-editor.org/rfc/rfc5652#section-3
+
+HornetData ::= SET OF Map
+
+Map ::= SEQUENCE {
+	index			INTEGER ({ hornet_map_index }),
+	sha			OCTET STRING ({ hornet_map_hash })
+} ({ hornet_next_map })
diff --git a/security/hornet/hornet_lsm.c b/security/hornet/hornet_lsm.c
new file mode 100644
index 0000000000000..a4d11fa5b0889
--- /dev/null
+++ b/security/hornet/hornet_lsm.c
@@ -0,0 +1,352 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Hornet Linux Security Module
+ *
+ * Author: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
+ *
+ * Copyright (C) 2026 Microsoft Corporation
+ */
+
+#include <linux/lsm_hooks.h>
+#include <uapi/linux/lsm.h>
+#include <linux/bpf.h>
+#include <linux/verification.h>
+#include <crypto/public_key.h>
+#include <linux/module_signature.h>
+#include <crypto/pkcs7.h>
+#include <linux/sort.h>
+#include <linux/asn1_decoder.h>
+#include <linux/oid_registry.h>
+#include "hornet.asn1.h"
+
+#define MAX_USED_MAPS 64
+
+struct hornet_maps {
+	bpfptr_t fd_array;
+};
+
+/* The only hashing algorithm available is SHA256 due to it be hardcoded
+ * in the bpf subsystem.
+ */
+
+struct hornet_parse_context {
+	int indexes[MAX_USED_MAPS];
+	bool skips[MAX_USED_MAPS];
+	unsigned char hashes[SHA256_DIGEST_SIZE * MAX_USED_MAPS];
+	int hash_count;
+};
+
+struct hornet_prog_security_struct {
+	int signed_hash_count;
+	unsigned char signed_hashes[SHA256_DIGEST_SIZE * MAX_USED_MAPS];
+};
+
+struct lsm_blob_sizes hornet_blob_sizes __ro_after_init = {
+	.lbs_bpf_prog = sizeof(struct hornet_prog_security_struct),
+};
+
+static inline struct hornet_prog_security_struct *
+hornet_bpf_prog_security(struct bpf_prog *prog)
+{
+	return prog->aux->security + hornet_blob_sizes.lbs_bpf_prog;
+}
+
+static int hornet_verify_hashes(struct hornet_maps *maps,
+				struct hornet_parse_context *ctx,
+				struct bpf_prog *prog)
+{
+	int map_fd;
+	u32 i;
+	struct bpf_map *map;
+	int err = 0;
+	unsigned char hash[SHA256_DIGEST_SIZE];
+	struct hornet_prog_security_struct *security = hornet_bpf_prog_security(prog);
+
+	for (i = 0; i < ctx->hash_count; i++) {
+		if (ctx->skips[i])
+			continue;
+
+		err = copy_from_bpfptr_offset(&map_fd, maps->fd_array,
+					      ctx->indexes[i] * sizeof(map_fd),
+					      sizeof(map_fd));
+		if (err != 0)
+			return LSM_INT_VERDICT_FAULT;
+
+		CLASS(fd, f)(map_fd);
+		if (fd_empty(f))
+			return LSM_INT_VERDICT_FAULT;
+		if (unlikely(fd_file(f)->f_op != &bpf_map_fops))
+			return LSM_INT_VERDICT_FAULT;
+
+		map = fd_file(f)->private_data;
+		if (!READ_ONCE(map->frozen))
+			return LSM_INT_VERDICT_FAULT;
+
+		if (!map->ops->map_get_hash)
+			return LSM_INT_VERDICT_FAULT;
+
+		if (map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, hash))
+			return LSM_INT_VERDICT_FAULT;
+
+		err = memcmp(hash, &ctx->hashes[i * SHA256_DIGEST_SIZE],
+			      SHA256_DIGEST_SIZE);
+		if (err)
+			return LSM_INT_VERDICT_UNEXPECTED;
+
+		memcpy(&security->signed_hashes[security->signed_hash_count * SHA256_DIGEST_SIZE],
+		       &ctx->hashes[i * SHA256_DIGEST_SIZE], SHA256_DIGEST_SIZE);
+		security->signed_hash_count++;
+	}
+	return LSM_INT_VERDICT_OK;
+}
+
+int hornet_next_map(void *context, size_t hdrlen,
+		     unsigned char tag,
+		     const void *value, size_t vlen)
+{
+	struct hornet_parse_context *ctx = (struct hornet_parse_context *)context;
+
+	if (++ctx->hash_count >= MAX_USED_MAPS)
+		return -EINVAL;
+	return 0;
+}
+
+int hornet_map_index(void *context, size_t hdrlen,
+		     unsigned char tag,
+		     const void *value, size_t vlen)
+{
+	struct hornet_parse_context *ctx = (struct hornet_parse_context *)context;
+
+	if (vlen != 1)
+		return -EINVAL;
+
+	ctx->indexes[ctx->hash_count] = *(u8 *)value;
+	return 0;
+}
+
+int hornet_map_hash(void *context, size_t hdrlen,
+		    unsigned char tag,
+		    const void *value, size_t vlen)
+
+{
+	struct hornet_parse_context *ctx = (struct hornet_parse_context *)context;
+
+	if (vlen != SHA256_DIGEST_SIZE && vlen != 0)
+		return -EINVAL;
+
+	if (vlen) {
+		ctx->skips[ctx->hash_count] = false;
+		memcpy(&ctx->hashes[ctx->hash_count * SHA256_DIGEST_SIZE], value, vlen);
+	} else
+		ctx->skips[ctx->hash_count] = true;
+
+	return 0;
+}
+
+static int hornet_check_program(struct bpf_prog *prog, union bpf_attr *attr,
+				struct bpf_token *token, bool is_kernel,
+				enum lsm_integrity_verdict *verdict)
+{
+	struct hornet_maps maps = {0};
+	bpfptr_t usig = make_bpfptr(attr->signature, is_kernel);
+	struct pkcs7_message *msg;
+	struct hornet_parse_context *ctx;
+	void *sig;
+	int err;
+	const void *authattrs;
+	size_t authattrs_len;
+	struct key *key;
+	key_ref_t user_key = ERR_PTR(-ENOKEY);
+
+	if (!attr->signature) {
+		*verdict = LSM_INT_VERDICT_UNSIGNED;
+		return 0;
+	}
+
+	if (!attr->signature_size) {
+		*verdict = LSM_INT_VERDICT_BADSIG;
+		return 0;
+	}
+
+	ctx = kzalloc(sizeof(struct hornet_parse_context), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+
+	maps.fd_array = make_bpfptr(attr->fd_array, is_kernel);
+	sig = kzalloc(attr->signature_size, GFP_KERNEL);
+	if (!sig) {
+		err = -ENOMEM;
+		goto out;
+	}
+	err = copy_from_bpfptr(sig, usig, attr->signature_size);
+	if (err != 0) {
+		err = -EFAULT;
+		goto cleanup_sig;
+	}
+
+	msg = pkcs7_parse_message(sig, attr->signature_size);
+	if (IS_ERR(msg)) {
+		*verdict = LSM_INT_VERDICT_BADSIG;
+		err = 0;
+		goto cleanup_sig;
+	}
+
+	if (system_keyring_id_check(attr->keyring_id) == 0)
+		key = (struct key *)(unsigned long)attr->keyring_id;
+	else {
+		user_key = lookup_user_key(attr->keyring_id, 0, KEY_DEFER_PERM_CHECK);
+		if (IS_ERR(user_key)) {
+			*verdict = LSM_INT_VERDICT_UNKNOWNKEY;
+			goto cleanup_msg;
+		}
+		key = key_ref_to_ptr(user_key);
+	}
+
+	if (verify_pkcs7_message_sig(prog->insnsi, prog->len * sizeof(struct bpf_insn), msg,
+				     key,
+				     VERIFYING_BPF_SIGNATURE,
+				     NULL, NULL)) {
+		*verdict = LSM_INT_VERDICT_BADSIG;
+		err = 0;
+		goto cleanup_msg;
+	}
+
+	if (pkcs7_get_authattr(msg, OID_hornet_data,
+			       &authattrs, &authattrs_len) == -ENODATA) {
+		*verdict = LSM_INT_VERDICT_PARTIALSIG;
+		err = 0;
+		goto cleanup_msg;
+	}
+
+	err = asn1_ber_decoder(&hornet_decoder, ctx, authattrs, authattrs_len);
+	if (err < 0 || authattrs == NULL) {
+		*verdict = LSM_INT_VERDICT_BADSIG;
+		err = 0;
+		goto cleanup_msg;
+	}
+
+	*verdict = hornet_verify_hashes(&maps, ctx, prog);
+	err = 0;
+
+cleanup_msg:
+	pkcs7_free_message(msg);
+	if (!IS_ERR(user_key))
+		key_put(key);
+cleanup_sig:
+	kfree(sig);
+out:
+	kfree(ctx);
+	return err;
+}
+
+static const struct lsm_id hornet_lsmid = {
+	.name = "hornet",
+	.id = LSM_ID_HORNET,
+};
+
+static int hornet_bpf_prog_load_integrity(struct bpf_prog *prog, union bpf_attr *attr,
+					  struct bpf_token *token, bool is_kernel)
+{
+	enum lsm_integrity_verdict verdict;
+	int result = hornet_check_program(prog, attr, token, is_kernel, &verdict);
+
+	if (result < 0)
+		return result;
+
+	return security_bpf_prog_load_post_integrity(prog, attr, token, is_kernel,
+						     &hornet_lsmid, verdict);
+}
+
+static int hornet_check_prog_maps(u32 ufd)
+{
+	CLASS(fd, f)(ufd);
+	struct bpf_prog *prog;
+	struct hornet_prog_security_struct *security;
+	unsigned char hash[SHA256_DIGEST_SIZE];
+	struct bpf_map *map;
+	int i, j;
+	bool found;
+	int covered_count = 0;
+
+	if (fd_empty(f))
+		return -EBADF;
+	if (fd_file(f)->f_op != &bpf_prog_fops)
+		return -EINVAL;
+
+	prog = fd_file(f)->private_data;
+	security = hornet_bpf_prog_security(prog);
+
+	if (!security->signed_hash_count)
+		return 0;
+
+	mutex_lock(&prog->aux->used_maps_mutex);
+
+	/* Verify every used_map has a matching signed hash */
+	for (j = 0; j < prog->aux->used_map_cnt; j++) {
+		map = prog->aux->used_maps[j];
+
+		if (!READ_ONCE(map->frozen) || !map->ops->map_get_hash)
+			continue;
+
+		if (map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, hash))
+			continue;
+
+		found = false;
+		for (i = 0; i < security->signed_hash_count; i++) {
+			if (memcmp(hash,
+				   &security->signed_hashes[i * SHA256_DIGEST_SIZE],
+				   SHA256_DIGEST_SIZE) == 0) {
+				found = true;
+				break;
+			}
+		}
+		if (!found) {
+			mutex_unlock(&prog->aux->used_maps_mutex);
+			return -EPERM;
+		}
+		covered_count++;
+	}
+
+	mutex_unlock(&prog->aux->used_maps_mutex);
+
+	/* Ensure all signed hashes were accounted for */
+	if (covered_count != security->signed_hash_count)
+		return -EPERM;
+
+	return 0;
+}
+
+static int hornet_bpf(int cmd, union bpf_attr *attr, unsigned int size, bool kernel)
+{
+	/* in horent_bpf(), anything that had originated from kernel space we assume
+	 * has already been checked, in some form or another, so we don't bother
+	 * checking the intergity of any maps. In hornet_bpf_prog_load_integrity(),
+	 * hornet doesn't make any opinion on that and delegates that to the downstream
+	 * policy enforcement.
+	 */
+
+	if (cmd != BPF_PROG_RUN)
+		return 0;
+	if (kernel)
+		return 0;
+
+	return hornet_check_prog_maps(attr->test.prog_fd);
+}
+
+static struct security_hook_list hornet_hooks[] __ro_after_init = {
+	LSM_HOOK_INIT(bpf_prog_load_integrity, hornet_bpf_prog_load_integrity),
+	LSM_HOOK_INIT(bpf, hornet_bpf),
+};
+
+static int __init hornet_init(void)
+{
+	pr_info("Hornet: eBPF signature verification enabled\n");
+	security_add_hooks(hornet_hooks, ARRAY_SIZE(hornet_hooks), &hornet_lsmid);
+	return 0;
+}
+
+DEFINE_LSM(hornet) = {
+	.id = &hornet_lsmid,
+	.blobs = &hornet_blob_sizes,
+	.init = hornet_init,
+};
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 05/10] lsm: security: Add additional enum values for bpf integrity checks
From: Blaise Boscaccy @ 2026-05-07 19:13 UTC (permalink / raw)
  To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
	Serge E. Hallyn, Mickaël Salaün, Günther Noack,
	Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
	Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
	linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260507191416.2984054-1-bboscaccy@linux.microsoft.com>

First add a generic LSM_INT_VERDICT_FAULT value to indicate a system
failure during checking. Second, add a LSM_INT_VERDICT_UNKNOWNKEY to
signal that the payload was signed with a key other than one that
exists in the secondary keyring. And finally add an
LSM_INT_VERDICT_UNEXPECTED enum value to indicate that a unexpected
hash value was encountered at some stage of verification.

Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
 include/linux/security.h | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/include/linux/security.h b/include/linux/security.h
index b3fd04baa78d0..4b4b8808f67de 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -106,6 +106,9 @@ enum lsm_integrity_verdict {
 	LSM_INT_VERDICT_OK,
 	LSM_INT_VERDICT_UNSIGNED,
 	LSM_INT_VERDICT_PARTIALSIG,
+	LSM_INT_VERDICT_UNKNOWNKEY,
+	LSM_INT_VERDICT_UNEXPECTED,
+	LSM_INT_VERDICT_FAULT,
 	LSM_INT_VERDICT_BADSIG,
 };
 
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 04/10] lsm: framework for BPF integrity verification
From: Blaise Boscaccy @ 2026-05-07 19:13 UTC (permalink / raw)
  To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
	Serge E. Hallyn, Mickaël Salaün, Günther Noack,
	Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
	Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
	linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260507191416.2984054-1-bboscaccy@linux.microsoft.com>

From: Paul Moore <paul@paul-moore.com>

Add a new LSM hook and two new LSM hook callbacks to support LSMs that
perform integrity verification, e.g. digital signature verification,
of BPF programs.

While the BPF subsystem does implement a signature verification scheme,
it does not satisfy a number of existing requirements, adding support
for BPF program integrity verification to the LSM framework allows
administrators to select additional integrity verification mechanisms
to meet these needs while also providing a mechanism for future
expansion.  Additional on why this is necessary can be found at the
lore archive link below:

https://lore.kernel.org/linux-security-module/CAHC9VhTQ_DR=ANzoDBjcCtrimV7XcCZVUsANPt=TjcvM4d-vjg@mail.gmail.com/

The LSM-based BPF integrity verification mechanism works within the
existing security_bpf_prog_load() hook called by the BPF subsystem.
It adds an additional dedicated integrity callback and a new LSM
hook/callback to be called from within LSMs implementing integrity
verification.

The first new callback, bpf_prog_load_integrity(), located within the
security_bpf_prog_load() hook, is necessary to ensure that the integrity
verification callbacks are executed before any of the existing LSMs
are executed via the bpf_prog_load() callback.  Reusing the existing
bpf_prog_load() callback for integrity verification could result in LSMs
not having access to the integrity verification results when asked to
authorize the BPF program load in the bpf_prog_load() callback.

The new LSM hook, security_bpf_prog_load_post_integrity(), is intended
to be called from within LSMs performing BPF program integrity
verification.  It is used to report the verdict of the integrity
verification to other LSMs enforcing access control policy on BPF
program loads.  LSMs enforcing such access controls should register a
bpf_prog_load_post_integrity() callback to receive integrity verdicts.

More information on these new callbacks and hook can be found in the
code comments in this patch.

Signed-off-by: Paul Moore <paul@paul-moore.com>
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
 include/linux/lsm_hook_defs.h |  5 +++
 include/linux/security.h      | 25 ++++++++++++
 security/security.c           | 75 +++++++++++++++++++++++++++++++++--
 3 files changed, 102 insertions(+), 3 deletions(-)

diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 8c42b4bde09c0..4971d3c36d5b4 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -434,6 +434,11 @@ LSM_HOOK(int, 0, bpf_prog, struct bpf_prog *prog)
 LSM_HOOK(int, 0, bpf_map_create, struct bpf_map *map, union bpf_attr *attr,
 	 struct bpf_token *token, bool kernel)
 LSM_HOOK(void, LSM_RET_VOID, bpf_map_free, struct bpf_map *map)
+LSM_HOOK(int, 0, bpf_prog_load_post_integrity, struct bpf_prog *prog,
+	 union bpf_attr *attr, struct bpf_token *token, bool kernel,
+	 const struct lsm_id *lsmid, enum lsm_integrity_verdict verdict)
+LSM_HOOK(int, 0, bpf_prog_load_integrity, struct bpf_prog *prog,
+	 union bpf_attr *attr, struct bpf_token *token, bool kernel)
 LSM_HOOK(int, 0, bpf_prog_load, struct bpf_prog *prog, union bpf_attr *attr,
 	 struct bpf_token *token, bool kernel)
 LSM_HOOK(void, LSM_RET_VOID, bpf_prog_free, struct bpf_prog *prog)
diff --git a/include/linux/security.h b/include/linux/security.h
index ee88dd2d2d1f7..b3fd04baa78d0 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -67,6 +67,7 @@ enum fs_value_type;
 struct watch;
 struct watch_notification;
 struct lsm_ctx;
+struct lsm_id;
 
 /* Default (no) options for the capable function */
 #define CAP_OPT_NONE 0x0
@@ -100,6 +101,14 @@ enum lsm_integrity_type {
 	LSM_INT_FSVERITY_BUILTINSIG_VALID,
 };
 
+enum lsm_integrity_verdict {
+	LSM_INT_VERDICT_NONE = 0,
+	LSM_INT_VERDICT_OK,
+	LSM_INT_VERDICT_UNSIGNED,
+	LSM_INT_VERDICT_PARTIALSIG,
+	LSM_INT_VERDICT_BADSIG,
+};
+
 /*
  * These are reasons that can be passed to the security_locked_down()
  * LSM hook. Lockdown reasons that protect kernel integrity (ie, the
@@ -2270,6 +2279,12 @@ extern int security_bpf_prog(struct bpf_prog *prog);
 extern int security_bpf_map_create(struct bpf_map *map, union bpf_attr *attr,
 				   struct bpf_token *token, bool kernel);
 extern void security_bpf_map_free(struct bpf_map *map);
+extern int security_bpf_prog_load_post_integrity(struct bpf_prog *prog,
+					union bpf_attr *attr,
+					struct bpf_token *token,
+					bool kernel,
+					const struct lsm_id *lsmid,
+					enum lsm_integrity_verdict verdict);
 extern int security_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr,
 				  struct bpf_token *token, bool kernel);
 extern void security_bpf_prog_free(struct bpf_prog *prog);
@@ -2304,6 +2319,16 @@ static inline int security_bpf_map_create(struct bpf_map *map, union bpf_attr *a
 static inline void security_bpf_map_free(struct bpf_map *map)
 { }
 
+static inline int security_bpf_prog_load_post_integrity(struct bpf_prog *prog,
+					  union bpf_attr *attr,
+					  struct bpf_token *token,
+					  bool kernel,
+					  const struct lsm_id *lsmid,
+					  enum lsm_integrity_verdict verdict)
+{
+	return 0;
+}
+
 static inline int security_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr,
 					 struct bpf_token *token, bool kernel)
 {
diff --git a/security/security.c b/security/security.c
index a26c1474e2e49..bb78f7e45a98f 100644
--- a/security/security.c
+++ b/security/security.c
@@ -5233,6 +5233,50 @@ int security_bpf_map_create(struct bpf_map *map, union bpf_attr *attr,
 	return rc;
 }
 
+/**
+ * security_bpf_prog_load_post_integrity() - Check if the BPF prog is allowed
+ * @prog: BPF program object
+ * @attr: BPF syscall attributes used to create BPF program
+ * @token: BPF token used to grant user access to BPF subsystem
+ * @kernel: whether or not call originated from kernel
+ * @lsmid: LSM ID of the LSM providing @verdict
+ * @verdict: result of the integrity verification
+ *
+ * See the comment block for the security_bpf_prog_load() LSM hook.
+ *
+ * This LSM hook is intended to be called from within the
+ * bpf_prog_load_integrity() callback that is part of the
+ * security_bpf_prog_load() hook; kernel subsystems outside the scope of the
+ * LSM framework should not call this hook directly.
+ *
+ * If the LSM calling into this hook receives a non-zero error code, it should
+ * return the same error code back to its caller.  If this hook returns a zero,
+ * it does not necessarily mean that all of the enabled LSMs have authorized
+ * the BPF program load, as there may be other LSMs implementing BPF integrity
+ * checks which have yet to execute.  However, if a zero is returned, the LSM
+ * calling into this hook should continue and return zero back to its caller.
+ *
+ * LSMs which implement the bpf_prog_load_post_integrity() callback and
+ * determine that a particular BPF program load is not authorized may choose to
+ * either return an error code for immediate rejection, or store their decision
+ * in their own LSM state attached to @prog, later returning an error code in
+ * the bpf_prog_load() callback.  An immediate error code return is in keeping
+ * with the "fail fast" practice, but waiting until the bpf_prog_load()
+ * callback allows the LSM to consider multiple different integrity verdicts.
+ *
+ * Return: Returns 0 on success, error on failure.
+ */
+int security_bpf_prog_load_post_integrity(struct bpf_prog *prog,
+					  union bpf_attr *attr,
+					  struct bpf_token *token,
+					  bool kernel,
+					  const struct lsm_id *lsmid,
+					  enum lsm_integrity_verdict verdict)
+{
+	return call_int_hook(bpf_prog_load_post_integrity, prog, attr, token,
+			     kernel, lsmid, verdict);
+}
+
 /**
  * security_bpf_prog_load() - Check if loading of BPF program is allowed
  * @prog: BPF program object
@@ -5241,8 +5285,24 @@ int security_bpf_map_create(struct bpf_map *map, union bpf_attr *attr,
  * @kernel: whether or not call originated from kernel
  *
  * Perform an access control check when the kernel loads a BPF program and
- * allocates associated BPF program object. This hook is also responsible for
- * allocating any required LSM state for the BPF program.
+ * allocates the associated BPF program object. This hook is also responsible
+ * for allocating any required LSM state for the BPF program.
+ *
+ * This hook calls two LSM callbacks: bpf_prog_load_integrity() and
+ * bpf_prog_load().  The bpf_prog_load_integrity() callback is for those LSMs
+ * that wish to implement integrity verifications of BPF programs, e.g.
+ * signature verification, while the bpf_prog_load() callback is for general
+ * authorization of the BPF program load.  Performing both verification and
+ * authorization in a single callback, with arbitrary LSM ordering, would be
+ * a challenge.
+ *
+ * LSMs which implement the bpf_prog_load_integrity() callback should call into
+ * the security_bpf_prog_load_post_integrity() hook with their integrity
+ * verdict.  LSMs which implement BPF program integrity policy can register a
+ * callback for the security_bpf_prog_load_post_integrity() hook and
+ * either update their own internal state based on the verdict, or immediately
+ * reject the BPF program load with an error code.  See the comment block for
+ * security_bpf_prog_load_post_integrity() for more information.
  *
  * Return: Returns 0 on success, error on failure.
  */
@@ -5255,9 +5315,18 @@ int security_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr,
 	if (unlikely(rc))
 		return rc;
 
+	rc = call_int_hook(bpf_prog_load_integrity, prog, attr, token, kernel);
+	if (unlikely(rc))
+		goto err;
+
 	rc = call_int_hook(bpf_prog_load, prog, attr, token, kernel);
 	if (unlikely(rc))
-		security_bpf_prog_free(prog);
+		goto err;
+
+	return rc;
+
+err:
+	security_bpf_prog_free(prog);
 	return rc;
 }
 
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 02/10] crypto: pkcs7: add ability to extract signed attributes by OID
From: Blaise Boscaccy @ 2026-05-07 19:13 UTC (permalink / raw)
  To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
	Serge E. Hallyn, Mickaël Salaün, Günther Noack,
	Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
	Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
	linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260507191416.2984054-1-bboscaccy@linux.microsoft.com>

From: James Bottomley <James.Bottomley@HansenPartnership.com>

Signers may add any information they like in signed attributes and
sometimes this information turns out to be relevant to specific
signing cases, so add an api pkcs7_get_authattr() to extract the value
of an authenticated attribute by specific OID.  The current
implementation is designed for the single signer use case and simply
terminates the search when it finds the relevant OID.

Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
 crypto/asymmetric_keys/Makefile       |  4 +-
 crypto/asymmetric_keys/pkcs7_aa.asn1  | 18 ++++++
 crypto/asymmetric_keys/pkcs7_parser.c | 81 +++++++++++++++++++++++++++
 include/crypto/pkcs7.h                |  4 ++
 4 files changed, 106 insertions(+), 1 deletion(-)
 create mode 100644 crypto/asymmetric_keys/pkcs7_aa.asn1

diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makefile
index bc65d3b98dcbf..f99b7169ae7cd 100644
--- a/crypto/asymmetric_keys/Makefile
+++ b/crypto/asymmetric_keys/Makefile
@@ -53,12 +53,14 @@ clean-files	+= pkcs8.asn1.c pkcs8.asn1.h
 obj-$(CONFIG_PKCS7_MESSAGE_PARSER) += pkcs7_message.o
 pkcs7_message-y := \
 	pkcs7.asn1.o \
+	pkcs7_aa.asn1.o \
 	pkcs7_parser.o \
 	pkcs7_trust.o \
 	pkcs7_verify.o
 
-$(obj)/pkcs7_parser.o: $(obj)/pkcs7.asn1.h
+$(obj)/pkcs7_parser.o: $(obj)/pkcs7.asn1.h $(obj)/pkcs7_aa.asn1.h
 $(obj)/pkcs7.asn1.o: $(obj)/pkcs7.asn1.c $(obj)/pkcs7.asn1.h
+$(obj)/pkcs7_aa.asn1.o: $(obj)/pkcs7_aa.asn1.c $(obj)/pkcs7_aa.asn1.h
 
 #
 # PKCS#7 parser testing key
diff --git a/crypto/asymmetric_keys/pkcs7_aa.asn1 b/crypto/asymmetric_keys/pkcs7_aa.asn1
new file mode 100644
index 0000000000000..7a8857bdf56e1
--- /dev/null
+++ b/crypto/asymmetric_keys/pkcs7_aa.asn1
@@ -0,0 +1,18 @@
+-- SPDX-License-Identifier: BSD-3-Clause
+--
+-- Copyright (C) 2009 IETF Trust and the persons identified as authors
+-- of the code
+--
+-- https://www.rfc-editor.org/rfc/rfc5652#section-3
+
+AA ::= 	CHOICE {
+	aaSet		[0] IMPLICIT AASet,
+	aaSequence	[2] EXPLICIT SEQUENCE OF AuthenticatedAttribute
+}
+
+AASet ::= SET OF AuthenticatedAttribute
+
+AuthenticatedAttribute ::= SEQUENCE {
+	type	OBJECT IDENTIFIER ({ pkcs7_aa_note_OID }),
+	values	SET OF ANY ({ pkcs7_aa_note_attr })
+}
diff --git a/crypto/asymmetric_keys/pkcs7_parser.c b/crypto/asymmetric_keys/pkcs7_parser.c
index 6e3ffdac83ace..d467866f7d930 100644
--- a/crypto/asymmetric_keys/pkcs7_parser.c
+++ b/crypto/asymmetric_keys/pkcs7_parser.c
@@ -15,6 +15,7 @@
 #include <crypto/public_key.h>
 #include "pkcs7_parser.h"
 #include "pkcs7.asn1.h"
+#include "pkcs7_aa.asn1.h"
 
 MODULE_DESCRIPTION("PKCS#7 parser");
 MODULE_AUTHOR("Red Hat, Inc.");
@@ -211,6 +212,86 @@ int pkcs7_get_content_data(const struct pkcs7_message *pkcs7,
 }
 EXPORT_SYMBOL_GPL(pkcs7_get_content_data);
 
+struct pkcs7_aa_context {
+	bool found;
+	enum OID oid_to_find;
+	const void *data;
+	size_t len;
+};
+
+int pkcs7_aa_note_OID(void *context, size_t hdrlen,
+		      unsigned char tag,
+		      const void *value, size_t vlen)
+{
+	struct pkcs7_aa_context *ctx = context;
+	enum OID oid = look_up_OID(value, vlen);
+
+	ctx->found = (oid == ctx->oid_to_find);
+
+	return 0;
+}
+
+int pkcs7_aa_note_attr(void *context, size_t hdrlen,
+		       unsigned char tag,
+		       const void *value, size_t vlen)
+{
+	struct pkcs7_aa_context *ctx = context;
+
+	if (ctx->found) {
+		ctx->data = value;
+		ctx->len = vlen;
+	}
+
+	return 0;
+}
+
+/**
+ * pkcs7_get_authattr - get authenticated attribute by OID
+ * @pkcs7: The preparsed PKCS#7 message
+ * @oid: the enum value of the OID to find
+ * @_data: Place to return a pointer to the attribute value
+ * @_len: length of the attribute value
+ *
+ * Searches the authenticated attributes until one is found with a
+ * matching OID.  Note that because the attributes are per signer
+ * there could be multiple signers with different values, but this
+ * routine will simply return the first one in parse order.
+ *
+ * Returns -ENODATA if the attribute can't be found
+ */
+int pkcs7_get_authattr(const struct pkcs7_message *pkcs7,
+		       enum OID oid,
+		       const void **_data, size_t *_len)
+{
+	struct pkcs7_signed_info *sinfo = pkcs7->signed_infos;
+	struct pkcs7_aa_context ctx;
+
+	ctx.data = NULL;
+	ctx.oid_to_find = oid;
+
+	for (; sinfo; sinfo = sinfo->next) {
+		int ret;
+
+		/* only extract OIDs from validated signers */
+		if (!sinfo->verified)
+			continue;
+
+		ret = asn1_ber_decoder(&pkcs7_aa_decoder, &ctx,
+				       sinfo->authattrs, sinfo->authattrs_len);
+		if (ret < 0 || ctx.data != NULL)
+			break;
+	}
+
+	if (!ctx.data)
+		return -ENODATA;
+
+	*_data = ctx.data;
+	*_len = ctx.len;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(pkcs7_get_authattr);
+
 /*
  * Note an OID when we find one for later processing when we know how
  * to interpret it.
diff --git a/include/crypto/pkcs7.h b/include/crypto/pkcs7.h
index 38ec7f5f90411..bd83202cd805c 100644
--- a/include/crypto/pkcs7.h
+++ b/include/crypto/pkcs7.h
@@ -25,6 +25,10 @@ extern void pkcs7_free_message(struct pkcs7_message *pkcs7);
 extern int pkcs7_get_content_data(const struct pkcs7_message *pkcs7,
 				  const void **_data, size_t *_datalen,
 				  size_t *_headerlen);
+extern int pkcs7_get_authattr(const struct pkcs7_message *pkcs7,
+			      enum OID oid,
+			      const void **_data, size_t *_len);
+
 
 /*
  * pkcs7_trust.c
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 03/10] crypto: pkcs7: add tests for pkcs7_get_authattr
From: Blaise Boscaccy @ 2026-05-07 19:13 UTC (permalink / raw)
  To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
	Serge E. Hallyn, Mickaël Salaün, Günther Noack,
	Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
	Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
	linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260507191416.2984054-1-bboscaccy@linux.microsoft.com>

From: James Bottomley <James.Bottomley@HansenPartnership.com>

Add example code to the test module pkcs7_key_type.c that verifies a
message and then pulls out a known authenticated attribute.

Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
Acked-by: David Howells <dhowells@redhat.com>
---
 crypto/asymmetric_keys/pkcs7_key_type.c | 44 ++++++++++++++++++++++++-
 1 file changed, 43 insertions(+), 1 deletion(-)

diff --git a/crypto/asymmetric_keys/pkcs7_key_type.c b/crypto/asymmetric_keys/pkcs7_key_type.c
index b930d3bbf1af5..e0b1ce0202f6d 100644
--- a/crypto/asymmetric_keys/pkcs7_key_type.c
+++ b/crypto/asymmetric_keys/pkcs7_key_type.c
@@ -12,6 +12,7 @@
 #include <linux/verification.h>
 #include <linux/key-type.h>
 #include <keys/user-type.h>
+#include <crypto/pkcs7.h>
 
 MODULE_LICENSE("GPL");
 MODULE_DESCRIPTION("PKCS#7 testing key type");
@@ -51,16 +52,57 @@ static int pkcs7_view_content(void *ctx, const void *data, size_t len,
 static int pkcs7_preparse(struct key_preparsed_payload *prep)
 {
 	enum key_being_used_for usage = pkcs7_usage;
+	int ret;
+	struct pkcs7_message *pkcs7;
+	const void *data;
+	size_t len;
 
 	if (usage >= NR__KEY_BEING_USED_FOR) {
 		pr_err("Invalid usage type %d\n", usage);
 		return -EINVAL;
 	}
 
-	return verify_pkcs7_signature(NULL, 0,
+	ret = verify_pkcs7_signature(NULL, 0,
 				      prep->data, prep->datalen,
 				      VERIFY_USE_SECONDARY_KEYRING, usage,
 				      pkcs7_view_content, prep);
+	if (ret)
+		return ret;
+
+	pkcs7 = pkcs7_parse_message(prep->data, prep->datalen);
+	if (IS_ERR(pkcs7)) {
+		pr_err("pkcs7 parse error\n");
+		return PTR_ERR(pkcs7);
+	}
+
+	/*
+	 * the parsed message has no trusted signer, so nothing should
+	 * be returned here
+	 */
+	ret = pkcs7_get_authattr(pkcs7, OID_messageDigest, &data, &len);
+	if (ret == 0) {
+		pr_err("OID returned when no trust in signer\n");
+		goto out;
+	}
+	/* add trust and check again */
+	ret = verify_pkcs7_message_sig(NULL, 0, pkcs7,
+				       VERIFY_USE_SECONDARY_KEYRING, usage,
+				       NULL, NULL);
+	if (ret) {
+		pr_err("verify_pkcs7_message_sig failed!!\n");
+		goto out;
+	}
+	/* now we should find the OID */
+	ret = pkcs7_get_authattr(pkcs7, OID_messageDigest, &data, &len);
+	if (ret) {
+		pr_err("Failed to get message digest\n");
+		goto out;
+	}
+	pr_info("Correctly Got message hash, size=%zu\n", len);
+
+ out:
+	pkcs7_free_message(pkcs7);
+	return 0;
 }
 
 /*
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 01/10] crypto: pkcs7: add flag for validated trust on a signed info block
From: Blaise Boscaccy @ 2026-05-07 19:13 UTC (permalink / raw)
  To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
	Serge E. Hallyn, Mickaël Salaün, Günther Noack,
	Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
	Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
	linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260507191416.2984054-1-bboscaccy@linux.microsoft.com>

From: James Bottomley <James.Bottomley@HansenPartnership.com>

Allow consumers of struct pkcs7_message to tell if any of the sinfo
fields has passed a trust validation.  Note that this does not happen
in parsing, pkcs7_validate_trust() must be explicitly called or called
via validate_pkcs7_trust().  Since the way to get this trusted pkcs7
object is via verify_pkcs7_message_sig, export that so modules can use
it.

Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
 certs/system_keyring.c                | 1 +
 crypto/asymmetric_keys/pkcs7_parser.h | 1 +
 crypto/asymmetric_keys/pkcs7_trust.c  | 1 +
 3 files changed, 3 insertions(+)

diff --git a/certs/system_keyring.c b/certs/system_keyring.c
index e0761436ec7f4..9bda49295bd02 100644
--- a/certs/system_keyring.c
+++ b/certs/system_keyring.c
@@ -380,6 +380,7 @@ int verify_pkcs7_message_sig(const void *data, size_t len,
 	pr_devel("<==%s() = %d\n", __func__, ret);
 	return ret;
 }
+EXPORT_SYMBOL(verify_pkcs7_message_sig);
 
 /**
  * verify_pkcs7_signature - Verify a PKCS#7-based signature on system data.
diff --git a/crypto/asymmetric_keys/pkcs7_parser.h b/crypto/asymmetric_keys/pkcs7_parser.h
index 6ef9f335bb17f..203062a33def6 100644
--- a/crypto/asymmetric_keys/pkcs7_parser.h
+++ b/crypto/asymmetric_keys/pkcs7_parser.h
@@ -20,6 +20,7 @@ struct pkcs7_signed_info {
 	unsigned	index;
 	bool		unsupported_crypto;	/* T if not usable due to missing crypto */
 	bool		blacklisted;
+	bool		verified; /* T if this signer has validated trust */
 
 	/* Message digest - the digest of the Content Data (or NULL) */
 	const void	*msgdigest;
diff --git a/crypto/asymmetric_keys/pkcs7_trust.c b/crypto/asymmetric_keys/pkcs7_trust.c
index 9a87c34ed1733..78ebfb6373b61 100644
--- a/crypto/asymmetric_keys/pkcs7_trust.c
+++ b/crypto/asymmetric_keys/pkcs7_trust.c
@@ -127,6 +127,7 @@ static int pkcs7_validate_trust_one(struct pkcs7_message *pkcs7,
 		for (p = sinfo->signer; p != x509; p = p->signer)
 			p->verified = true;
 	}
+	sinfo->verified = true;
 	kleave(" = 0");
 	return 0;
 }
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 00/10] Reintroduce Hornet LSM
From: Blaise Boscaccy @ 2026-05-07 19:13 UTC (permalink / raw)
  To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
	Serge E. Hallyn, Mickaël Salaün, Günther Noack,
	Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
	Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
	linux-doc, linux-kernel, bpf, Song Liu

This patch series introduces the next iteration of the Hornet LSM.
Hornet’s goal is to provide a secure and extensible in-kernel
signature verification mechanism for eBPF programs.

Hornet addresses concerns from users who require strict audit trails and
verification guarantees for eBPF programs, especially in
security-sensitive environments. Many production systems need assurance
that only authorized, unmodified eBPF programs are loaded into the
kernel. Hornet provides this assurance through cryptographic signature
verification.

The currently accepted loader-plus-map signature verification scheme,
mandated by Alexei and KP, is simple to implement and generally
acceptable if users and administrators are satisfied with it. However,
verifying both the loader and the maps offers additional benefits
beyond verifying the loader alone:

1. Security and Audit Integrity

A key advantage is that the LSM hook for authorizing BPF program loads
can operate after signature verification. This ensures:

* Access control decisions are based on verified signature status.
* Accurate system state measurement and logging.
* Log entries claiming a verified signature are truthful, avoiding
  misleading records where only the loader was verified while the actual
  BPF program verification occurs later without logging.

2. TOCTOU Attack Prevention

The current map hash implementation may be vulnerable to a TOCTOU
attack because it allows unfrozen maps to cache a previously
calculated hash. The accepted “trusted loader” scheme cannot detect
this and may permit loading altered maps.

3. Supply Chain Integrity

Verify that eBPF programs and their associated map data have not been
modified since they were built and signed, in the kernel proper, may
aid in protecting against supply chain attacks.

This approach addresses concerns from users who require strict audit
trails and verification guarantees, especially in security-sensitive
environments. Map hashes for extended verification are passed via the
existing PKCS#7 UAPI and verified by the crypto subsystem. Hornet then
calculates the program’s verification state.  Hornet itself does not
enforce a policy on whether unsigned or partially signed programs
should be rejected. It delegates that decision to downstream LSMs
hook, making it a composable building block in a larger security
architecture.

Changes in V7:
- minor IPE fixes
- tooling fixes in gen_sig
- checkpatch fixes

Link to V6: https://lore.kernel.org/linux-security-module/20260429191431.2345448-1-bboscaccy@linux.microsoft.com/

Changes in V6:
- Doc cleanup
- Error handling improvements
- Support for shareable maps

Link to V5: https://lore.kernel.org/linux-security-module/20260420212653.438685-1-bboscaccy@linux.microsoft.com/

Changes in V5:
- Added IPE documentation

Link to V4: https://lore.kernel.org/linux-security-module/20260416173500.176716-1-bboscaccy@linux.microsoft.com/

Changes in V4:
- IPE integration
- Arbitrary keyring support

Link to V3: https://lore.kernel.org/linux-security-module/20260326060655.2550595-1-bboscaccy@linux.microsoft.com/

Changes in V3:
- Updated for signed attribute patch series changes
- Added some new result enum values
- Minor documentation clarification
- Misc style fixes
- Added missing signed-off-by tags

Link to V2: https://lore.kernel.org/linux-security-module/20260227233930.2418522-1-bboscaccy@linux.microsoft.com/

Changes in V2:
- Addressed possible TocTou races in hash verification
- Improved documentation and tooling
- Added Alexie's nack

Link to RFC: https://lore.kernel.org/linux-security-module/20251211021257.1208712-1-bboscaccy@linux.microsoft.com/

Blaise Boscaccy (6):
  lsm: security: Add additional enum values for bpf integrity checks
  security: Hornet LSM
  hornet: Introduce gen_sig
  hornet: Add a light skeleton data extractor scripts
  selftests/hornet: Add a selftest for the Hornet LSM
  ipe: Add BPF program load policy enforcement via Hornet integration

James Bottomley (3):
  crypto: pkcs7: add flag for validated trust on a signed info block
  crypto: pkcs7: add ability to extract signed attributes by OID
  crypto: pkcs7: add tests for pkcs7_get_authattr

Paul Moore (1):
  lsm: framework for BPF integrity verification

 Documentation/admin-guide/LSM/Hornet.rst     | 323 +++++++++++++++
 Documentation/admin-guide/LSM/index.rst      |   1 +
 Documentation/admin-guide/LSM/ipe.rst        | 162 +++++++-
 Documentation/security/ipe.rst               |  68 ++++
 MAINTAINERS                                  |   9 +
 certs/system_keyring.c                       |   1 +
 crypto/asymmetric_keys/Makefile              |   4 +-
 crypto/asymmetric_keys/pkcs7_aa.asn1         |  18 +
 crypto/asymmetric_keys/pkcs7_key_type.c      |  44 +-
 crypto/asymmetric_keys/pkcs7_parser.c        |  81 ++++
 crypto/asymmetric_keys/pkcs7_parser.h        |   1 +
 crypto/asymmetric_keys/pkcs7_trust.c         |   1 +
 include/crypto/pkcs7.h                       |   4 +
 include/linux/lsm_hook_defs.h                |   5 +
 include/linux/oid_registry.h                 |   3 +
 include/linux/security.h                     |  28 ++
 include/uapi/linux/lsm.h                     |   1 +
 scripts/Makefile                             |   1 +
 scripts/hornet/Makefile                      |   5 +
 scripts/hornet/extract-insn.sh               |  27 ++
 scripts/hornet/extract-map.sh                |  27 ++
 scripts/hornet/extract-skel.sh               |  27 ++
 scripts/hornet/gen_sig.c                     | 401 +++++++++++++++++++
 scripts/hornet/write-sig.sh                  |  27 ++
 security/Kconfig                             |   3 +-
 security/Makefile                            |   1 +
 security/hornet/Kconfig                      |  13 +
 security/hornet/Makefile                     |   7 +
 security/hornet/hornet.asn1                  |  12 +
 security/hornet/hornet_lsm.c                 | 352 ++++++++++++++++
 security/ipe/Kconfig                         |  15 +
 security/ipe/audit.c                         |  15 +
 security/ipe/eval.c                          |  93 ++++-
 security/ipe/eval.h                          |  11 +
 security/ipe/hooks.c                         |  63 +++
 security/ipe/hooks.h                         |  15 +
 security/ipe/ipe.c                           |  14 +
 security/ipe/ipe.h                           |   3 +
 security/ipe/policy.h                        |  14 +
 security/ipe/policy_parser.c                 |  27 ++
 security/security.c                          |  75 +++-
 tools/testing/selftests/Makefile             |   1 +
 tools/testing/selftests/hornet/Makefile      |  63 +++
 tools/testing/selftests/hornet/loader.c      |  21 +
 tools/testing/selftests/hornet/trivial.bpf.c |  33 ++
 45 files changed, 2112 insertions(+), 8 deletions(-)
 create mode 100644 Documentation/admin-guide/LSM/Hornet.rst
 create mode 100644 crypto/asymmetric_keys/pkcs7_aa.asn1
 create mode 100644 scripts/hornet/Makefile
 create mode 100755 scripts/hornet/extract-insn.sh
 create mode 100755 scripts/hornet/extract-map.sh
 create mode 100755 scripts/hornet/extract-skel.sh
 create mode 100644 scripts/hornet/gen_sig.c
 create mode 100755 scripts/hornet/write-sig.sh
 create mode 100644 security/hornet/Kconfig
 create mode 100644 security/hornet/Makefile
 create mode 100644 security/hornet/hornet.asn1
 create mode 100644 security/hornet/hornet_lsm.c
 create mode 100644 tools/testing/selftests/hornet/Makefile
 create mode 100644 tools/testing/selftests/hornet/loader.c
 create mode 100644 tools/testing/selftests/hornet/trivial.bpf.c

-- 
2.53.0


^ permalink raw reply

* Re: [PATCH v5 08/11] leds: rgb: add support for Samsung S2M series PMIC RGB LED device
From: Lee Jones @ 2026-05-07 19:00 UTC (permalink / raw)
  To: Kaustabh Chakraborty
  Cc: Pavel Machek, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	MyungJoo Ham, Chanwoo Choi, Sebastian Reichel,
	Krzysztof Kozlowski, André Draszik, Alexandre Belloni,
	Jonathan Corbet, Shuah Khan, Nam Tran,
	Łukasz Lebiedziński, linux-leds, devicetree,
	linux-kernel, linux-pm, linux-samsung-soc, linux-rtc, linux-doc
In-Reply-To: <20260424-s2mu005-pmic-v5-8-fcbc9da5a004@disroot.org>

On Fri, 24 Apr 2026, Kaustabh Chakraborty wrote:

> Add support for the RGB LEDs found in certain Samsung S2M series PMICs.
> The device has three LED channels, controlled as a single device. These
> LEDs are typically used as status indicators in mobile phones.
> 
> The driver includes initial support for the S2MU005 PMIC RGB LEDs.
> 
> Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
> ---
>  drivers/leds/rgb/Kconfig        |  11 +
>  drivers/leds/rgb/Makefile       |   1 +
>  drivers/leds/rgb/leds-s2m-rgb.c | 446 ++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 458 insertions(+)
> 
> diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig
> index 28ef4c487367c..30051342f4e4d 100644
> --- a/drivers/leds/rgb/Kconfig
> +++ b/drivers/leds/rgb/Kconfig
> @@ -75,6 +75,17 @@ config LEDS_QCOM_LPG
>  
>  	  If compiled as a module, the module will be named leds-qcom-lpg.
>  
> +config LEDS_S2M_RGB
> +	tristate "Samsung S2M series PMICs RGB LED support"
> +	depends on LEDS_CLASS
> +	depends on MFD_SEC_CORE
> +	select REGMAP_IRQ
> +	help
> +	  This option enables support for the S2MU005 RGB LEDs. These
> +	  devices have three LED channels, with 8-bit brightness control
> +	  for each channel. It's usually found in mobile phones as

"The S2MU005 is ..."

> +	  status indicators.
> +
>  config LEDS_MT6370_RGB
>  	tristate "LED Support for MediaTek MT6370 PMIC"
>  	depends on MFD_MT6370
> diff --git a/drivers/leds/rgb/Makefile b/drivers/leds/rgb/Makefile
> index be45991f63f50..98050e1aa4255 100644
> --- a/drivers/leds/rgb/Makefile
> +++ b/drivers/leds/rgb/Makefile
> @@ -6,4 +6,5 @@ obj-$(CONFIG_LEDS_LP5812)		+= leds-lp5812.o
>  obj-$(CONFIG_LEDS_NCP5623)		+= leds-ncp5623.o
>  obj-$(CONFIG_LEDS_PWM_MULTICOLOR)	+= leds-pwm-multicolor.o
>  obj-$(CONFIG_LEDS_QCOM_LPG)		+= leds-qcom-lpg.o
> +obj-$(CONFIG_LEDS_S2M_RGB)		+= leds-s2m-rgb.o
>  obj-$(CONFIG_LEDS_MT6370_RGB)		+= leds-mt6370-rgb.o
> diff --git a/drivers/leds/rgb/leds-s2m-rgb.c b/drivers/leds/rgb/leds-s2m-rgb.c
> new file mode 100644
> index 0000000000000..51d12f2ef762a
> --- /dev/null
> +++ b/drivers/leds/rgb/leds-s2m-rgb.c
> @@ -0,0 +1,446 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * RGB LED Driver for Samsung S2M series PMICs.
> + *
> + * Copyright (c) 2015 Samsung Electronics Co., Ltd
> + * Copyright (c) 2026 Kaustabh Chakraborty <kauschluss@disroot.org>
> + */
> +
> +#include <linux/container_of.h>
> +#include <linux/led-class-multicolor.h>
> +#include <linux/mfd/samsung/core.h>
> +#include <linux/mfd/samsung/s2mu005.h>
> +#include <linux/minmax.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +
> +struct s2m_rgb {
> +	struct device *dev;
> +	struct regmap *regmap;
> +	struct led_classdev_mc mc;
> +	enum sec_device_type device_type;
> +	/*
> +	 * The mutex object prevents race conditions when evaluation and
> +	 * application of LED pattern state.
> +	 */
> +	struct mutex lock;
> +	/*
> +	 * State variables representing the current LED pattern, these only to
> +	 * be accessed when lock is held.
> +	 */
> +	u8 ramp_up;
> +	u8 ramp_dn;
> +	u8 stay_hi;
> +	u8 stay_lo;
> +};
> +
> +static struct led_classdev_mc *to_s2m_mc(struct led_classdev *cdev)
> +{
> +	return container_of(cdev, struct led_classdev_mc, led_cdev);
> +}
> +
> +static struct s2m_rgb *to_s2m_rgb(struct led_classdev_mc *mc)
> +{
> +	return container_of(mc, struct s2m_rgb, mc);
> +}
> +
> +static const u32 s2mu005_rgb_lut_ramp[] = {
> +	0,	100,	200,	300,	400,	500,	600,	700,
> +	800,	1000,	1200,	1400,	1600,	1800,	2000,	2200,
> +};
> +
> +static const u32 s2mu005_rgb_lut_stay_hi[] = {
> +	100,	200,	300,	400,	500,	750,	1000,	1250,
> +	1500,	1750,	2000,	2250,	2500,	2750,	3000,	3250,
> +};
> +
> +static const u32 s2mu005_rgb_lut_stay_lo[] = {
> +	0,	500,	1000,	1500,	2000,	2500,	3000,	3500,
> +	4000,	4500,	5000,	6000,	7000,	8000,	10000,	12000,
> +};
> +
> +static int s2mu005_rgb_apply_params(struct s2m_rgb *rgb)
> +{
> +	struct regmap *regmap = rgb->regmap;
> +	unsigned int ramp_val = 0;
> +	unsigned int stay_val = 0;
> +	int ret;
> +	int i;
> +
> +	ramp_val |= FIELD_PREP(S2MU005_RGB_CH_RAMP_UP, rgb->ramp_up);
> +	ramp_val |= FIELD_PREP(S2MU005_RGB_CH_RAMP_DN, rgb->ramp_dn);
> +
> +	stay_val |= FIELD_PREP(S2MU005_RGB_CH_STAY_HI, rgb->stay_hi);
> +	stay_val |= FIELD_PREP(S2MU005_RGB_CH_STAY_LO, rgb->stay_lo);
> +
> +	ret = regmap_write(regmap, S2MU005_REG_RGB_EN, S2MU005_RGB_RESET);
> +	if (ret < 0) {
> +		dev_err(rgb->dev, "failed to reset RGB LEDs\n");
> +		return ret;
> +	}
> +
> +	for (i = 0; i < rgb->mc.num_colors; i++) {

for (int i = 0; ...)

> +		ret = regmap_write(regmap, S2MU005_REG_RGB_CH_CTRL(i),
> +				   rgb->mc.subled_info[i].brightness);
> +		if (ret < 0) {
> +			dev_err(rgb->dev, "failed to set LED brightness\n");
> +			return ret;
> +		}
> +
> +		ret = regmap_write(regmap, S2MU005_REG_RGB_CH_RAMP(i), ramp_val);
> +		if (ret < 0) {
> +			dev_err(rgb->dev, "failed to set ramp timings\n");
> +			return ret;
> +		}
> +
> +		ret = regmap_write(regmap, S2MU005_REG_RGB_CH_STAY(i), stay_val);
> +		if (ret < 0) {
> +			dev_err(rgb->dev, "failed to set stay timings\n");
> +			return ret;
> +		}
> +	}
> +
> +	ret = regmap_update_bits(regmap, S2MU005_REG_RGB_EN, S2MU005_RGB_SLOPE,
> +				 S2MU005_RGB_SLOPE_SMOOTH);
> +	if (ret < 0) {
> +		dev_err(rgb->dev, "failed to set ramp slope\n");
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int s2mu005_rgb_reset_params(struct s2m_rgb *rgb)
> +{
> +	struct regmap *regmap = rgb->regmap;
> +	int ret;
> +
> +	ret = regmap_write(regmap, S2MU005_REG_RGB_EN, S2MU005_RGB_RESET);
> +	if (ret < 0) {
> +		dev_err(rgb->dev, "failed to reset RGB LEDs\n");
> +		return ret;
> +	}
> +
> +	rgb->ramp_up = 0;
> +	rgb->ramp_dn = 0;
> +	rgb->stay_hi = 0;
> +	rgb->stay_lo = 0;
> +
> +	return 0;
> +}
> +
> +static int s2m_rgb_lut_calc_timing(const u32 *lut, const size_t len,
> +				   const u32 req_time, u8 *idx)
> +{
> +	int lo = 0;
> +	int hi = len - 2;
> +
> +	/* Bounds checking */
> +	if (req_time < lut[0] || req_time > lut[len - 1])
> +		return -EINVAL;
> +
> +	/*
> +	 * Perform a binary search to pick the best timing from the LUT.
> +	 *
> +	 * The search algorithm picks two consecutive elements of the
> +	 * LUT and tries to search the pair between which the requested
> +	 * time lies.
> +	 */
> +	while (lo <= hi) {
> +		*idx = (lo + hi) / 2;
> +
> +		if ((lut[*idx] <= req_time) && (req_time <= lut[*idx + 1]))
> +			break;
> +
> +		if ((req_time < lut[*idx]) && (req_time < lut[*idx + 1]))
> +			hi = *idx - 1;
> +		else
> +			lo = *idx + 1;
> +	}
> +
> +	/*
> +	 * The searched timing is always less than the requested time. At
> +	 * times, the succeeding timing in the LUT is closer thus more
> +	 * accurate. Adjust the resulting value if that's the case.
> +	 */
> +	if (abs(req_time - lut[*idx]) > abs(lut[*idx + 1] - req_time))
> +		(*idx)++;

As much as I appreciate the comments, most of the function is pretty
unreadable.  Are you able to use better variable nomenclature and layout
to better describe your aims?

> +	return 0;
> +}
> +
> +static int s2m_rgb_pattern_set(struct led_classdev *cdev, struct led_pattern *pattern,
> +			       u32 len, int repeat)
> +{
> +	struct s2m_rgb *rgb = to_s2m_rgb(to_s2m_mc(cdev));
> +	const u32 *lut_ramp_up, *lut_ramp_dn, *lut_stay_hi, *lut_stay_lo;
> +	size_t lut_ramp_up_len, lut_ramp_dn_len, lut_stay_hi_len, lut_stay_lo_len;
> +	int brightness_peak = 0;
> +	u32 time_hi = 0, time_lo = 0;
> +	bool ramp_up_en, ramp_dn_en;
> +	int ret;
> +	int i;
> +
> +	/*
> +	 * The typical pattern supported by this device can be
> +	 * represented with the following graph:
> +	 *
> +	 *  255 T ''''''-.                         .-'''''''-.
> +	 *      |         '.                     .'           '.
> +	 *      |           \                   /               \
> +	 *      |            '.               .'                 '.
> +	 *      |              '-...........-'                     '-
> +	 *    0 +----------------------------------------------------> time (s)
> +	 *
> +	 *       <---- HIGH ----><-- LOW --><-------- HIGH --------->
> +	 *       <-----><-------><---------><-------><-----><------->
> +	 *       stay_hi ramp_dn   stay_lo   ramp_up stay_hi ramp_dn
> +	 *
> +	 * There are two states, named HIGH and LOW. HIGH has a non-zero
> +	 * brightness level, while LOW is of zero brightness. The
> +	 * pattern provided should mention only one zero and non-zero
> +	 * brightness level. The hardware always starts the pattern from
> +	 * the HIGH state, as shown in the graph.
> +	 *
> +	 * The HIGH state can be divided in three somewhat equal timings:
> +	 * ramp_up, stay_hi, and ramp_dn. The LOW state has only one
> +	 * timing: stay_lo.
> +	 */
> +
> +	/* Only indefinitely looping patterns are supported. */
> +	if (repeat != -1)
> +		return -EINVAL;
> +
> +	/* Pattern should consist of at least two tuples. */
> +	if (len < 2)
> +		return -EINVAL;
> +
> +	for (i = 0; i < len; i++) {

for (int i = 0; ...) would be preferable.

> +		int brightness = pattern[i].brightness;
> +		u32 delta_t = pattern[i].delta_t;
> +
> +		if (brightness) {
> +			/*
> +			 * The pattern shold define only one non-zero
> +			 * brightness in the HIGH state. The device
> +			 * doesn't have any provisions to handle
> +			 * multiple peak brightness levels.
> +			 */
> +			if (brightness_peak && brightness_peak != brightness)
> +				return -EINVAL;
> +
> +			brightness_peak = brightness;
> +			time_hi += delta_t;
> +			ramp_dn_en = !!delta_t;
> +		} else {
> +			time_lo += delta_t;
> +			ramp_up_en = !!delta_t;
> +		}
> +	}
> +
> +	switch (rgb->device_type) {
> +	case S2MU005:
> +		lut_ramp_up = s2mu005_rgb_lut_ramp;
> +		lut_ramp_up_len = ARRAY_SIZE(s2mu005_rgb_lut_ramp);
> +		lut_ramp_dn = s2mu005_rgb_lut_ramp;
> +		lut_ramp_dn_len = ARRAY_SIZE(s2mu005_rgb_lut_ramp);
> +		lut_stay_hi = s2mu005_rgb_lut_stay_hi;
> +		lut_stay_hi_len = ARRAY_SIZE(s2mu005_rgb_lut_stay_hi);
> +		lut_stay_lo = s2mu005_rgb_lut_stay_lo;
> +		lut_stay_lo_len = ARRAY_SIZE(s2mu005_rgb_lut_stay_lo);
> +		break;
> +	default:
> +		/* execution shouldn't reach here */

Instead of a comment, perhaps a WARN_ON_ONCE(1); or similar would be
more robust here to catch unexpected device types?

> +		break;
> +	}
> +
> +	mutex_lock(&rgb->lock);
> +
> +	/*
> +	 * The timings ramp_up, stay_hi, and ramp_dn of the HIGH state
> +	 * are roughly equal. Firstly, calculate and set timings for
> +	 * ramp_up and ramp_dn (making sure they're exactly equal).
> +	 */
> +	rgb->ramp_up = 0;
> +	rgb->ramp_dn = 0;
> +
> +	if (ramp_up_en) {
> +		ret = s2m_rgb_lut_calc_timing(lut_ramp_up, lut_ramp_up_len, time_hi / 3,
> +					      &rgb->ramp_up);
> +		if (ret < 0)
> +			goto param_fail;
> +	}
> +
> +	if (ramp_dn_en) {
> +		ret = s2m_rgb_lut_calc_timing(lut_ramp_dn, lut_ramp_dn_len, time_hi / 3,
> +					      &rgb->ramp_dn);
> +		if (ret < 0)
> +			goto param_fail;
> +	}
> +
> +	/*
> +	 * Subtract the allocated ramp timings from time_hi (and also
> +	 * making sure it doesn't underflow!). The remaining time is
> +	 * allocated to stay_hi.
> +	 */
> +	time_hi -= min(time_hi, lut_ramp_up[rgb->ramp_up]);
> +	time_hi -= min(time_hi, lut_ramp_dn[rgb->ramp_dn]);
> +
> +	ret = s2m_rgb_lut_calc_timing(lut_stay_hi, lut_stay_hi_len, time_hi, &rgb->stay_hi);
> +	if (ret < 0)
> +		goto param_fail;
> +
> +	ret = s2m_rgb_lut_calc_timing(lut_stay_lo, lut_stay_lo_len, time_lo, &rgb->stay_lo);
> +	if (ret < 0)
> +		goto param_fail;
> +
> +	led_mc_calc_color_components(&rgb->mc, brightness_peak);
> +	switch (rgb->device_type) {
> +	case S2MU005:
> +		ret = s2mu005_rgb_apply_params(rgb);
> +		break;
> +	default:
> +		/* execution shouldn't reach here */
> +		break;
> +	}
> +	if (ret < 0)
> +		goto param_fail;

Are we expecting positive values in these 'ret's?

If not if (!ret) will do.

> +
> +	mutex_unlock(&rgb->lock);
> +
> +	return 0;
> +
> +param_fail:
> +	rgb->ramp_up = 0;
> +	rgb->ramp_dn = 0;
> +	rgb->stay_hi = 0;
> +	rgb->stay_lo = 0;
> +
> +	mutex_unlock(&rgb->lock);
> +
> +	return ret;
> +}
> +
> +static int s2m_rgb_pattern_clear(struct led_classdev *cdev)
> +{
> +	struct s2m_rgb *rgb = to_s2m_rgb(to_s2m_mc(cdev));
> +	int ret = 0;
> +
> +	mutex_lock(&rgb->lock);
> +
> +	switch (rgb->device_type) {
> +	case S2MU005:
> +		ret = s2mu005_rgb_reset_params(rgb);
> +		break;
> +	default:
> +		/* execution shouldn't reach here */
> +		break;

As above.

And a single branch switch () makes little sense.

> +	}
> +
> +	mutex_unlock(&rgb->lock);
> +
> +	return ret;
> +}
> +
> +static int s2m_rgb_brightness_set(struct led_classdev *cdev, enum led_brightness value)
> +{
> +	struct s2m_rgb *rgb = to_s2m_rgb(to_s2m_mc(cdev));
> +	int ret = 0;
> +
> +	if (!value)
> +		return s2m_rgb_pattern_clear(cdev);
> +
> +	mutex_lock(&rgb->lock);
> +
> +	led_mc_calc_color_components(&rgb->mc, value);
> +	switch (rgb->device_type) {
> +	case S2MU005:
> +		ret = s2mu005_rgb_apply_params(rgb);
> +		break;
> +	default:
> +		/* execution shouldn't reach here */
> +		break;
> +	}
> +
> +	mutex_unlock(&rgb->lock);
> +
> +	return ret;
> +}
> +
> +static struct mc_subled s2mu005_rgb_subled_info[] = {

const?

> +	{ .channel = 0, .color_index = LED_COLOR_ID_BLUE },
> +	{ .channel = 1, .color_index = LED_COLOR_ID_GREEN },
> +	{ .channel = 2, .color_index = LED_COLOR_ID_RED },
> +};
> +
> +static int s2m_rgb_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct sec_pmic_dev *pmic_drvdata = dev_get_drvdata(dev->parent);
> +	struct s2m_rgb *rgb;
> +	struct led_init_data init_data = {};
> +	int ret;
> +
> +	rgb = devm_kzalloc(dev, sizeof(*rgb), GFP_KERNEL);
> +	if (!rgb)
> +		return -ENOMEM;
> +
> +	platform_set_drvdata(pdev, rgb);
> +	rgb->dev = dev;
> +	rgb->regmap = pmic_drvdata->regmap_pmic;
> +	rgb->device_type = platform_get_device_id(pdev)->driver_data;

We don't tend to use these object oriented-type constructs in the
kernel.  Also, we have helper functions of extracting driver_data.
Please use them.

> +
> +	switch (rgb->device_type) {
> +	case S2MU005:
> +		rgb->mc.subled_info = s2mu005_rgb_subled_info;
> +		rgb->mc.num_colors = ARRAY_SIZE(s2mu005_rgb_subled_info);
> +		break;
> +	default:
> +		return dev_err_probe(dev, -ENODEV, "device type %d is not supported by driver\n",
> +				     pmic_drvdata->device_type);

Small point, but for consistency, would it be better to print
`rgb->device_type` here, since that is the value being checked in the
switch statement?

Also, same single branch comment as before.

> +	}
> +
> +	rgb->mc.led_cdev.max_brightness = 255;
> +	rgb->mc.led_cdev.brightness_set_blocking = s2m_rgb_brightness_set;
> +	rgb->mc.led_cdev.pattern_set = s2m_rgb_pattern_set;
> +	rgb->mc.led_cdev.pattern_clear = s2m_rgb_pattern_clear;
> +
> +	ret = devm_mutex_init(dev, &rgb->lock);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "failed to create mutex lock\n");
> +
> +	init_data.fwnode = of_fwnode_handle(dev->of_node);
> +	ret = devm_led_classdev_multicolor_register_ext(dev, &rgb->mc, &init_data);
> +	if (ret < 0)
> +		return dev_err_probe(dev, ret, "failed to create LED device\n");
> +
> +	return 0;
> +}
> +
> +static const struct platform_device_id s2m_rgb_id_table[] = {
> +	{ "s2mu005-rgb", S2MU005 },
> +	{ /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(platform, s2m_rgb_id_table);
> +
> +static const struct of_device_id s2m_rgb_of_match_table[] = {
> +	{ .compatible = "samsung,s2mu005-rgb", .data = (void *)S2MU005 },
> +	{ /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(of, s2m_rgb_of_match_table);
> +
> +static struct platform_driver s2m_rgb_driver = {
> +	.driver = {
> +		.name = "s2m-rgb",
> +	},
> +	.probe = s2m_rgb_probe,
> +	.id_table = s2m_rgb_id_table,
> +};
> +module_platform_driver(s2m_rgb_driver);
> +
> +MODULE_DESCRIPTION("RGB LED Driver For Samsung S2M Series PMICs");

"for"

> +MODULE_AUTHOR("Kaustabh Chakraborty <kauschluss@disroot.org>");
> +MODULE_LICENSE("GPL");

-- 
Lee Jones

^ permalink raw reply

* Re: [PATCH v17 11/11] Documentation: cxl: Document CXL protocol error handling
From: Jonathan Cameron @ 2026-05-07 18:51 UTC (permalink / raw)
  To: Terry Bowman
  Cc: dave, dave.jiang, alison.schofield, djbw, bhelgaas, shiju.jose,
	ming.li, Smita.KoralahalliChannabasappa, rrichter, dan.carpenter,
	PradeepVineshReddy.Kodamati, lukas, Benjamin.Cheatham,
	sathyanarayanan.kuppuswamy, vishal.l.verma, alucerop, ira.weiny,
	corbet, rafael, xueshuai, linux-cxl, linux-kernel, linux-pci,
	linux-acpi, linux-doc
In-Reply-To: <20260505173029.2718246-12-terry.bowman@amd.com>

On Tue, 5 May 2026 12:30:29 -0500
Terry Bowman <terry.bowman@amd.com> wrote:

> Add Documentation/driver-api/cxl/linux/protocol-error-handling.rst
> describing the end-to-end CXL protocol error path: AER ingress, the
> AER-CXL kfifo handoff, the cxl_core consumer worker, RCD/RCH special
> cases, severity policy, trace events, and a source code map.
> 
> This documents the architecture introduced by the preceding patches in
> this series.
> 
> This was generated by claude-opus-4.7.

Maybe too much?  I got bored reading it and stopped which is probably
not the best sign.

A few formatting related comments inline.

Thanks,

J
> 
> Assisted-by: Claude:claude-opus-4.7
> Signed-off-by: Terry Bowman <terry.bowman@amd.com>
> ---
>  Documentation/driver-api/cxl/index.rst        |   1 +
>  .../cxl/linux/protocol-error-handling.rst     | 440 ++++++++++++++++++
>  2 files changed, 441 insertions(+)
>  create mode 100644 Documentation/driver-api/cxl/linux/protocol-error-handling.rst
> 
> diff --git a/Documentation/driver-api/cxl/index.rst b/Documentation/driver-api/cxl/index.rst
> index 3dfae1d310ca..6861b2e5726a 100644
> --- a/Documentation/driver-api/cxl/index.rst
> +++ b/Documentation/driver-api/cxl/index.rst
> @@ -42,6 +42,7 @@ that have impacts on each other.  The docs here break up configurations steps.
>     linux/dax-driver
>     linux/memory-hotplug
>     linux/access-coordinates
> +   linux/protocol-error-handling
>  
>  .. toctree::
>     :maxdepth: 2
> diff --git a/Documentation/driver-api/cxl/linux/protocol-error-handling.rst b/Documentation/driver-api/cxl/linux/protocol-error-handling.rst
> new file mode 100644
> index 000000000000..4d6f33f0ed31
> --- /dev/null
> +++ b/Documentation/driver-api/cxl/linux/protocol-error-handling.rst
> @@ -0,0 +1,440 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +==============================
> +CXL Protocol Error Handling
> +==============================
> +
> +This document describes how the kernel detects, classifies, dispatches,
> +logs, and recovers from CXL protocol errors signaled through the PCIe
> +Advanced Error Reporting (AER) interface. It covers both Virtual
> +Hierarchy (VH) topologies (Root Ports, Upstream/Downstream Switch
> +Ports, and Endpoints) and Restricted CXL Host (RCH) topologies
> +(Root Complex Event Collectors driving Restricted CXL Devices).

Odd drifting wrapping. I thought only humans did that. I guess it's common
enough in kernel docs maybe it learn it!  Anyhow, I think Docs are 80 char
limit in which case something like:

This document describes how the kernel detects, classifies, dispatches, logs,
and recovers from CXL protocol errors signaled through the PCIe Advanced Error
Reporting (AER) interface. It covers both Virtual Hierarchy (VH) topologies
(Root Ports, Upstream/Downstream Switch Ports, and Endpoints) and Restricted
CXL Host (RCH) topologies (Root Complex Event Collectors driving Restricted
CXL Devices).

Maybe t was intentional to keep lines similar lengths and brackets on last one?
I'm not sure..

> +
> +It is intended for kernel developers maintaining or extending
> +``drivers/pci/pcie/aer*.c``, ``drivers/cxl/core/ras.c``, and the
> +related plumbing in ``include/linux/aer.h``.
> +
> +
> +Background
> +==========
> +
> +A CXL device reports protocol-layer failures (CXL.cachemem RAS) as
> +PCIe AER **Internal Errors**: ``PCI_ERR_COR_INTERNAL`` for correctable
> +events and ``PCI_ERR_UNC_INTN`` for uncorrectable events. From the AER
> +core's point of view these look like ordinary PCIe AER messages, but
> +their semantics are CXL-specific: the actual fault information lives
> +in CXL RAS capability registers, not in the PCIe AER status registers.
> +
> +Historically, native CXL.cachemem RAS handling was implemented only
> +for CXL Endpoints and for RCH Downstream Ports. CXL Root Ports,
> +Upstream Switch Ports, and Downstream Switch Ports were not covered.
> +This left the kernel unable to log or react to protocol errors
> +signaled by switch components.

I'd drop the historical bit.  Not sure it adds value and these tend to
become stale (like all the 'New Courts' in my local Uni. Some of those are
500+ years old :)

> +
> +The unified CXL protocol error path closes that gap by routing every
> +CXL Internal Error through a single producer/consumer pipeline shared
> +by all CXL device types.

The unified CXL Protocol path routes every ...
(so no historical gap - as we don't care now you fixed it ;)

Similar follows for some other parts - I might not have called them all out.

> +
> +
> +Architecture overview
> +=====================
> +
> +CXL protocol error handling is implemented as a distinct error plane
> +layered on top of the existing PCIe AER infrastructure. The two planes

(drop existing - same why do we need the history theme)

> +are kept separate:
> +
> +* The **PCIe AER plane** continues to handle native PCIe errors
** handles native  
> +  (Receiver overflows, malformed TLPs, completion timeouts, and so
> +  on). This is unchanged.
> +
> +* The **CXL protocol error plane** owns CXL Internal Errors. The AER
> +  core forwards them to ``cxl_core`` via a dedicated kfifo; ``cxl_core``
> +  then dispatches to CE/UE handlers and drives the recovery and
> +  panic policy.
> +
> +The boundary between the two planes is ``is_cxl_error()`` in

I think you can drop the `` and the automarkup.py magic in the kernel docs build
will make that :c:func::is_cxl_error or something along those lines to
both pretty print it and hopefully match autobuilt kernel-doc (assuming
we include it anywhere for cxl)


> +===============
> +
> +The diagram below shows the full path from an AER interrupt through
> +producer classification, kfifo handoff, and consumer dispatch.
> +
> +.. code-block:: text
> +
> +   +-------------------------------------------------------------------------+
> +   |                  CXL Internal Error Packet Flow                         |
> +   |    From PCIe AER Interrupt to CXL Protocol Error Handling and Logging   |
> +   +-------------------------------------------------------------------------+
> +
> +      CXL device (RP / USP / DSP / EP / RCD) raises AER Internal Error
> +      (correctable PCI_ERR_COR_INTERNAL or uncorrectable PCI_ERR_UNC_INTN)
> +                      |
> +                      v
> +      +-------------------------------------------------------------+
> +      |    PCIe Root Port AER MSI/MSI-X interrupt fires             |
> +      +-------------------------------------------------------------+
> +                      |
> +      ============= drivers/pci/pcie/aer.c (AER core) =============
> +                      |
> +                      v
> +           +---------------------------------+
> +           |  aer_irq()  /  aer_isr()        |  (top + threaded handler)
> +           +---------------------------------+
> +                      |
> +                      v
> +           +---------------------------------+
> +           |  aer_isr_one_error()            |
> +           |  aer_isr_one_error_type()       |
> +           +---------------------------------+
> +                      |
> +                      v
> +          +------------------------------------------+
> +          |  aer_get_device_error_info()             |
> +          |  - reads PCI_ERR_COR_STATUS              |
> +          |  - reads PCI_ERR_UNCOR_STATUS  (*if RP/  |
> +          |    RCEC/DSP, or non-fatal severity)      |
> +          |  - sets info->is_cxl = pcie_is_cxl(dev)  |
> +          +------------------------------------------+
> +                      |
> +                      v
> +           +---------------------------------+
> +           |  handle_error_source(dev, info) |
> +           +---------------------------------+
> +              |                          |
> +              |  is_cxl_error()          +--->  pci_aer_handle_error()
> +              |  (CXL device + Internal)        (native PCIe AER path,
> +              v                                  not covered here)
> +      +-------------------------------------------------------------+
> +      | Topology dispatch within AER core:                          |
> +      |                                                             |
> +      |   - VH topology  (RP / USP / DSP / EP)                      |
> +      |     -> drivers/pci/pcie/aer_cxl_vh.c                        |
> +      |                                                             |
> +      |   - RCH topology (RCEC iterates RCDs under it)              |
> +      |     -> drivers/pci/pcie/aer_cxl_rch.c                       |
> +      +-------------------------------------------------------------+
> +           |                                            |
> +           | VH path                            RCH path (RCEC AER)
> +           v                                            v
> +      ============= aer_cxl_vh.c (VH      ============= aer_cxl_rch.c (RCH
> +                    producer) =============              producer) ==========
> +           |                                            |
> +           v                                            v
> +      +-----------------------------+         +-------------------------------+
> +      | cxl_forward_error(pdev,info)|         | cxl_rch_handle_error_iter()   |
> +      |  - if AER_CORRECTABLE:      |         |  - iterate each RCD pdev      |
> +      |     clear PCI_ERR_COR_STATUS|         |    beneath the RCEC           |
> +      |  - pci_dev_get(pdev)        |         |  - call cxl_forward_error()   |
> +      |  - build cxl_proto_err_     |         |    for each RCD               |
> +      |    work_data                |         |    (same producer helper as   |
> +      |    { pdev, severity }       |         |     the VH path uses)         |
> +      |  - kfifo_in_spinlocked(...) |         +-------------------------------+
> +      |  - schedule_work(...)       |                       |
> +      +-----------------------------+                       |
> +              |                                             |
> +              +-----------------+---------------------------+
> +                                |
> +                                v
> +                    +--------------------------+
> +                    |     AER-CXL kfifo        |
> +                    |     (work_struct)        |
> +                    +--------------------------+
> +                                |
> +                                v
> +      ============= drivers/cxl/core/ras.c (consumer worker) =======
> +                                |
> +                                v
> +      +-------------------------------------------------------------+
> +      | cxl_proto_err_work_fn() (workqueue handler)                 |
> +      |   for_each_cxl_proto_err(&wd, __cxl_proto_err_work_fn)      |
> +      +-------------------------------------------------------------+
> +                      |
> +                      v
> +      +-------------------------------------------------------------+
> +      | __cxl_proto_err_work_fn(wd)                                 |
> +      |   port = find_cxl_port_by_dev(&pdev->dev, &dport)           |
> +      |   cxl_handle_proto_error(pdev, port, dport, severity)       |
> +      |   pci_dev_put(pdev)                                         |
> +      +-------------------------------------------------------------+
> +                      |
> +                      v
> +      +-------------------------------------------------------------+
> +      | cxl_handle_proto_error()                                    |
> +      +-------------------------------------------------------------+
> +           |                                            |
> +      pci_pcie_type ==                          pci_pcie_type !=
> +      PCI_EXP_TYPE_RC_END                       PCI_EXP_TYPE_RC_END
> +      (RCD Endpoint)                            (VH: RP/USP/DSP/EP)
> +           |                                            |
> +           v                                            |
> +      +-------------------------------------+           |
> +      | cxl_handle_rdport_errors(pdev)      |           |
> +      |   - process RCH Downstream Port's   |           |
> +      |     RAS register block first        |           |
> +      |   - cxl_handle_cor_ras() for CE     |           |
> +      |   - cxl_handle_ras() for UE         |           |
> +      |     (log only; does NOT panic)      |           |
> +      +-------------------------------------+           |
> +           |                                            |
> +           +--------------------+-----------------------+
> +                                |
> +                                v
> +                   +-----------------------------+
> +                   | severity == AER_CORRECTABLE |
> +                   +-----------------------------+
> +                         |                  |
> +                         yes                no
> +                         v                  v
> +            +----------------------+   +-------------------------+
> +            | cxl_handle_cor_ras() |   | cxl_do_recovery()       |
> +            |  - emit cxl_aer_     |   | (described below)       |
> +            |    correctable_      |   +-------------------------+
> +            |    error trace       |
> +            | pcie_clear_device_   |
> +            |   status()           |
> +            +----------------------+
> +
> +                    +-------------------------------+
> +                    | cxl_do_recovery()             |
> +                    |  if pci_dev_is_disconnected:  |
> +                    |    panic("CXL cachemem err.") |
> +                    |                               |
> +                    |  ue = cxl_handle_ras()        |
> +                    |    -> emit                    |
> +                    |       cxl_aer_uncorrectable_  |
> +                    |       error trace event       |
> +                    |                               |
> +                    |  if (ue):                     |
> +                    |    panic("CXL cachemem err.") |
> +                    |                               |
> +                    |  pcie_clear_device_status()   |
> +                    |  pci_aer_clear_nonfatal_status|
> +                    |  pci_aer_clear_fatal_status   |
> +                    +-------------------------------+

Pretty diagram but maybe far too much given we have the code?

> +
> +
> +Severity policy
> +===============
> +
> +The kernel's response to a CXL protocol error depends on the AER
> +severity reported by the device and on the result of inspecting the
> +CXL RAS registers.
> +


^ permalink raw reply

* Re: [PATCH v5 07/11] leds: flash: add support for Samsung S2M series PMIC flash LED device
From: Kaustabh Chakraborty @ 2026-05-07 18:33 UTC (permalink / raw)
  To: Lee Jones, Kaustabh Chakraborty
  Cc: Pavel Machek, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	MyungJoo Ham, Chanwoo Choi, Sebastian Reichel,
	Krzysztof Kozlowski, André Draszik, Alexandre Belloni,
	Jonathan Corbet, Shuah Khan, Nam Tran,
	Łukasz Lebiedziński, linux-leds, devicetree,
	linux-kernel, linux-pm, linux-samsung-soc, linux-rtc, linux-doc
In-Reply-To: <20260507164654.GS305027@google.com>

On 2026-05-07 17:46 +01:00, Lee Jones wrote:
> On Fri, 24 Apr 2026, Kaustabh Chakraborty wrote:
>
>> Add support for flash LEDs in certain Samsung S2M series PMICs.
>> The device has two channels for LEDs, typically for the back and front
>> cameras in mobile devices. Both channels can be independently
>> controlled, and can be operated in torch or flash modes.
>> 
>> The driver includes initial support for the S2MU005 PMIC flash LEDs.
>> 
>> Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
>> ---
>>  drivers/leds/flash/Kconfig          |  12 ++
>>  drivers/leds/flash/Makefile         |   1 +
>>  drivers/leds/flash/leds-s2m-flash.c | 358 ++++++++++++++++++++++++++++++++++++
>>  3 files changed, 371 insertions(+)
>> 
>> diff --git a/drivers/leds/flash/Kconfig b/drivers/leds/flash/Kconfig
>> index 5e08102a67841..be62e05277429 100644
>> --- a/drivers/leds/flash/Kconfig
>> +++ b/drivers/leds/flash/Kconfig
>> @@ -114,6 +114,18 @@ config LEDS_RT8515
>>  	  To compile this driver as a module, choose M here: the module
>>  	  will be called leds-rt8515.
>>  
>> +config LEDS_S2M_FLASH
>> +	tristate "Samsung S2M series PMICs flash/torch LED support"
>> +	depends on LEDS_CLASS
>> +	depends on MFD_SEC_CORE
>> +	depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
>
> The `|| !V4L2_FLASH_LED_CLASS` part of this dependency makes it
> unconditionally true. Was this intended? Perhaps this dependency can be
> removed entirely.

Right? Similar lines are also present in entries of other drivers too.
It is indeed weird, but I disregarded my doubts and added it anyway.

>> +	select REGMAP_IRQ
>> +	help
>> +	  This option enables support for the flash/torch LEDs found in
>> +	  certain Samsung S2M series PMICs, such as the S2MU005. It has
>> +	  a LED channel dedicated for every physical LED. The LEDs can
>> +	  be controlled in flash and torch modes.
>> +
>>  config LEDS_SGM3140
>>  	tristate "LED support for the SGM3140"
>>  	depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
>> diff --git a/drivers/leds/flash/Makefile b/drivers/leds/flash/Makefile
>> index 712fb737a428e..44e6c1b4beb37 100644
>> --- a/drivers/leds/flash/Makefile
>> +++ b/drivers/leds/flash/Makefile
>> @@ -10,6 +10,7 @@ obj-$(CONFIG_LEDS_MAX77693)	+= leds-max77693.o
>>  obj-$(CONFIG_LEDS_QCOM_FLASH)	+= leds-qcom-flash.o
>>  obj-$(CONFIG_LEDS_RT4505)	+= leds-rt4505.o
>>  obj-$(CONFIG_LEDS_RT8515)	+= leds-rt8515.o
>> +obj-$(CONFIG_LEDS_S2M_FLASH)	+= leds-s2m-flash.o
>>  obj-$(CONFIG_LEDS_SGM3140)	+= leds-sgm3140.o
>>  obj-$(CONFIG_LEDS_SY7802)	+= leds-sy7802.o
>>  obj-$(CONFIG_LEDS_TPS6131X)	+= leds-tps6131x.o
>> diff --git a/drivers/leds/flash/leds-s2m-flash.c b/drivers/leds/flash/leds-s2m-flash.c
>> new file mode 100644
>> index 0000000000000..177d23b432ce6
>> --- /dev/null
>> +++ b/drivers/leds/flash/leds-s2m-flash.c
>> @@ -0,0 +1,358 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Flash and Torch LED Driver for Samsung S2M series PMICs.
>> + *
>> + * Copyright (c) 2015 Samsung Electronics Co., Ltd
>> + * Copyright (c) 2026 Kaustabh Chakraborty <kauschluss@disroot.org>
>> + */
>> +
>> +#include <linux/container_of.h>
>> +#include <linux/led-class-flash.h>
>> +#include <linux/mfd/samsung/core.h>
>> +#include <linux/mfd/samsung/s2mu005.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/regmap.h>
>> +#include <media/v4l2-flash-led-class.h>
>> +
>> +#define MAX_CHANNELS	2
>> +
>> +struct s2m_led {
>> +	struct regmap *regmap;
>> +	struct led_classdev_flash fled;
>> +	struct v4l2_flash *v4l2_flash;
>> +	/*
>> +	 * The mutex object prevents the concurrent access of flash control
>> +	 * registers by the LED and V4L2 subsystems.
>> +	 */
>> +	struct mutex lock;
>> +	unsigned int reg_enable;
>> +	u8 channel;
>> +	u8 flash_brightness;
>> +	u8 flash_timeout;
>> +};
>> +
>> +static struct s2m_led *to_s2m_led(struct led_classdev_flash *fled)
>> +{
>> +	return container_of(fled, struct s2m_led, fled);
>> +}
>> +
>> +static struct led_classdev_flash *to_s2m_fled(struct led_classdev *cdev)
>> +{
>> +	return container_of(cdev, struct led_classdev_flash, led_cdev);
>> +}
>> +
>> +static int s2m_fled_flash_brightness_set(struct led_classdev_flash *fled, u32 brightness)
>> +{
>> +	struct s2m_led *led = to_s2m_led(fled);
>> +	struct led_flash_setting *setting = &fled->brightness;
>> +
>> +	led->flash_brightness = (brightness - setting->min) / setting->step;
>> +
>> +	return 0;
>> +}
>> +
>> +static int s2m_fled_flash_timeout_set(struct led_classdev_flash *fled, u32 timeout)
>> +{
>> +	struct s2m_led *led = to_s2m_led(fled);
>> +	struct led_flash_setting *setting = &fled->timeout;
>> +
>> +	led->flash_timeout = (timeout - setting->min) / setting->step;
>> +
>> +	return 0;
>> +}
>> +
>> +#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
>> +static int s2m_fled_flash_external_strobe_set(struct v4l2_flash *v4l2_flash, bool enable)
>> +{
>> +	struct s2m_led *led = to_led(v4l2_flash->fled_cdev);
>
> What is to_led()?
>
> Was this tested?

I honestly don't know what happened here, and yes this (well, not this
precisely) was tested. I had changed something later? Don't know, its
odd. Will fix.

>> +	mutex_lock(&led->lock);
>> +
>> +	led->fled.ops->strobe_set(&led->fled, enable);
>> +
>> +	mutex_unlock(&led->lock);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct v4l2_flash_ops s2m_fled_v4l2_flash_ops = {
>> +	.external_strobe_set = s2m_fled_flash_external_strobe_set,
>> +};
>> +#else
>> +static const struct v4l2_flash_ops s2m_fled_v4l2_flash_ops;
>> +#endif
>> +
>> +static void s2m_fled_v4l2_flash_release(void *v4l2_flash)
>> +{
>> +	v4l2_flash_release(v4l2_flash);
>> +}
>> +
>> +static int s2mu005_fled_torch_brightness_set(struct led_classdev *cdev, enum led_brightness value)
>> +{
>> +	struct s2m_led *led = to_s2m_led(to_s2m_fled(cdev));
>> +	int ret;
>> +
>> +	mutex_lock(&led->lock);
>> +
>> +	if (!value) {
>> +		ret = regmap_clear_bits(led->regmap, led->reg_enable,
>> +					S2MU005_FLED_TORCH_EN(led->channel));
>> +		if (ret < 0)
>> +			dev_err(cdev->dev, "failed to disable torch LED\n");
>> +		goto unlock;
>> +	}
>> +
>> +	ret = regmap_update_bits(led->regmap, S2MU005_REG_FLED_CH_CTRL1(led->channel),
>> +				 S2MU005_FLED_TORCH_IOUT,
>> +				 FIELD_PREP(S2MU005_FLED_TORCH_IOUT, value - 1));
>> +	if (ret < 0) {
>
> Is a positive number even possible?

As per the docs, no. Will fix here and in other instances as well.

>> +		dev_err(cdev->dev, "failed to set torch current\n");
>> +		goto unlock;
>> +	}
>> +
>> +	ret = regmap_set_bits(led->regmap, led->reg_enable, S2MU005_FLED_TORCH_EN(led->channel));
>> +	if (ret < 0) {
>> +		dev_err(cdev->dev, "failed to enable torch LED\n");
>> +		goto unlock;
>> +	}
>> +
>> +unlock:
>> +	mutex_unlock(&led->lock);
>> +
>> +	return ret;
>> +}
>> +
>> +static int s2mu005_fled_flash_strobe_set(struct led_classdev_flash *fled, bool state)
>> +{
>> +	struct s2m_led *led = to_s2m_led(fled);
>> +	int ret;
>> +
>> +	mutex_lock(&led->lock);
>> +
>> +	ret = regmap_clear_bits(led->regmap, led->reg_enable, S2MU005_FLED_FLASH_EN(led->channel));
>> +	if (ret < 0) {
>> +		dev_err(fled->led_cdev.dev, "failed to disable flash LED\n");
>> +		goto unlock;
>> +	}
>> +
>> +	if (!state)
>> +		goto unlock;
>> +
>> +	ret = regmap_update_bits(led->regmap, S2MU005_REG_FLED_CH_CTRL0(led->channel),
>> +				 S2MU005_FLED_FLASH_IOUT,
>> +				 FIELD_PREP(S2MU005_FLED_FLASH_IOUT, led->flash_brightness));
>> +	if (ret < 0) {
>> +		dev_err(fled->led_cdev.dev, "failed to set flash brightness\n");
>> +		goto unlock;
>> +	}
>> +
>> +	ret = regmap_update_bits(led->regmap, S2MU005_REG_FLED_CH_CTRL3(led->channel),
>> +				 S2MU005_FLED_FLASH_TIMEOUT,
>> +				 FIELD_PREP(S2MU005_FLED_FLASH_TIMEOUT, led->flash_timeout));
>> +	if (ret < 0) {
>> +		dev_err(fled->led_cdev.dev, "failed to set flash timeout\n");
>> +		goto unlock;
>> +	}
>> +
>> +	ret = regmap_set_bits(led->regmap, led->reg_enable, S2MU005_FLED_FLASH_EN(led->channel));
>> +	if (ret < 0) {
>> +		dev_err(fled->led_cdev.dev, "failed to enable flash LED\n");
>> +		goto unlock;
>> +	}
>> +
>> +unlock:
>> +	mutex_unlock(&led->lock);
>> +
>> +	return 0;
>
> It seems like this function swallows error codes.
>
> Better if they're propagated properly.
>
>> +}
>> +
>> +static int s2mu005_fled_flash_strobe_get(struct led_classdev_flash *fled, bool *state)
>> +{
>> +	struct s2m_led *led = to_s2m_led(fled);
>> +	u32 val;
>> +	int ret;
>> +
>> +	mutex_lock(&led->lock);
>> +
>> +	ret = regmap_read(led->regmap, S2MU005_REG_FLED_STATUS, &val);
>> +	if (ret < 0) {
>> +		dev_err(fled->led_cdev.dev, "failed to fetch LED status");
>
> Missed '/n'.
>
>> +		goto unlock;
>> +	}
>> +
>> +	*state = !!(val & S2MU005_FLED_FLASH_STATUS(led->channel));
>> +
>> +unlock:
>> +	mutex_unlock(&led->lock);
>> +
>> +	return ret;
>> +}
>> +
>> +static const struct led_flash_ops s2mu005_fled_flash_ops = {
>> +	.flash_brightness_set = s2m_fled_flash_brightness_set,
>> +	.timeout_set = s2m_fled_flash_timeout_set,
>> +	.strobe_set = s2mu005_fled_flash_strobe_set,
>> +	.strobe_get = s2mu005_fled_flash_strobe_get,
>> +};
>> +
>> +static int s2mu005_fled_init(struct s2m_led *led, struct device *dev, struct regmap *regmap,
>> +			     unsigned int nr_channels)
>> +{
>> +	unsigned int val;
>> +	int i, ret;
>> +
>> +	/* Enable the LED channels. */
>> +	ret = regmap_set_bits(regmap, S2MU005_REG_FLED_CTRL1, S2MU005_FLED_CH_EN);
>> +	if (ret < 0)
>> +		return dev_err_probe(dev, ret, "failed to enable LED channels\n");
>> +
>> +	ret = regmap_read(regmap, S2MU005_REG_ID, &val);
>> +	if (ret < 0)
>> +		return dev_err_probe(dev, ret, "failed to read revision\n");
>> +
>> +	for (i = 0; i < nr_channels; i++) {
>
> for (int i = 0; i < nr_channels; i++)

Is that allowed in the kernel source? I have never seen variable
declaration in a for loop.

>> +		/*
>> +		 * Read the revision register. Revision EVT0 has the register
>> +		 * at CTRL4, while EVT1 and higher have it at CTRL6.
>> +		 */
>> +		if (FIELD_GET(S2MU005_ID_MASK, val) == 0)
>
> Why not remove the " == 0" and reverse the branches?

My intention was to make it explicit that the value used is an integer
value, as opposed to a boolean.

>> +			led[i].reg_enable = S2MU005_REG_FLED_CTRL4;
>> +		else
>> +			led[i].reg_enable = S2MU005_REG_FLED_CTRL6;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int s2mu005_fled_init_channel(struct s2m_led *led, struct device *dev,
>> +				     struct fwnode_handle *fwnp)
>> +{
>> +	struct led_classdev *cdev = &led->fled.led_cdev;
>> +	struct led_init_data init_data = {};
>> +	struct v4l2_flash_config v4l2_cfg = {};
>> +	int ret;
>> +
>> +	cdev->max_brightness = 16;
>> +	cdev->brightness_set_blocking = s2mu005_fled_torch_brightness_set;
>> +	cdev->flags |= LED_DEV_CAP_FLASH;
>> +
>> +	led->fled.timeout.min = 62000;
>> +	led->fled.timeout.step = 62000;
>> +	led->fled.timeout.max = 992000;
>> +	led->fled.timeout.val = 992000;
>> +
>> +	led->fled.brightness.min = 25000;
>> +	led->fled.brightness.step = 25000;
>> +	led->fled.brightness.max = 375000; /* 400000 causes flickering */
>> +	led->fled.brightness.val = 375000;
>> +
>> +	s2m_fled_flash_timeout_set(&led->fled, led->fled.timeout.val);
>> +	s2m_fled_flash_brightness_set(&led->fled, led->fled.brightness.val);
>> +
>> +	led->fled.ops = &s2mu005_fled_flash_ops;
>> +
>> +	init_data.fwnode = fwnp;
>> +	ret = devm_led_classdev_flash_register_ext(dev, &led->fled, &init_data);
>> +	if (ret < 0)
>> +		return dev_err_probe(dev, ret, "failed to create LED flash device\n");
>> +
>> +	v4l2_cfg.intensity.min = led->fled.timeout.min;
>> +	v4l2_cfg.intensity.step = led->fled.timeout.step;
>> +	v4l2_cfg.intensity.max = led->fled.timeout.max;
>> +	v4l2_cfg.intensity.val = led->fled.timeout.val;
>
> Is it correct to configure the V4L2 intensity settings from the timeout
> values?  I would expect these to be based on the brightness settings.

Stupid me. Admittedly, I am unable to test the v4l2 functionality
properly for now. I will remove all related code in the next revision.
Will add them back when they're needed and I'm able to test.

>> +
>> +	v4l2_cfg.has_external_strobe = true;
>> +
>> +	led->v4l2_flash = v4l2_flash_init(dev, fwnp, &led->fled, &s2m_fled_v4l2_flash_ops,
>> +					  &v4l2_cfg);
>> +	if (IS_ERR(led->v4l2_flash)) {
>> +		v4l2_flash_release(led->v4l2_flash);
>
> So you're going to try and release an error?
>
>> +		return dev_err_probe(dev, PTR_ERR(led->v4l2_flash),
>> +				     "failed to create V4L2 flash device\n");
>> +	}
>> +
>> +	ret = devm_add_action_or_reset(dev, (void *)s2m_fled_v4l2_flash_release, led->v4l2_flash);
>> +	if (ret < 0)
>> +		return dev_err_probe(dev, ret, "failed to add cleanup action\n");
>> +
>> +	return 0;
>> +}
>> +
>> +static int s2m_fled_probe(struct platform_device *pdev)
>> +{
>> +	struct device *dev = &pdev->dev;
>> +	struct sec_pmic_dev *ddata = dev_get_drvdata(dev->parent);
>> +	struct s2m_led *led;
>> +	int ret;
>> +
>> +	led = devm_kzalloc(dev, sizeof(*led) * MAX_CHANNELS, GFP_KERNEL);
>> +	if (!led)
>> +		return -ENOMEM;
>> +
>> +	switch (platform_get_device_id(pdev)->driver_data) {
>> +	case S2MU005:
>> +		ret = s2mu005_fled_init(led, dev, ddata->regmap_pmic, MAX_CHANNELS);
>> +		if (ret)
>> +			return ret;
>> +		break;
>> +	default:
>> +		return dev_err_probe(dev, -ENODEV, "device type %d is not supported by driver\n",
>> +				     ddata->device_type);
>> +	}
>
> Will this be expanded in the very near future?
>
> If not, having a switch () with only one entry seems odd.

I have plans to introduce support for S2MU004 at one point. I'll change
it now, and re-introduce the switch block later.

> if (platform_get_device_id(pdev)->driver_data != S2MU005)
> 	dev_err_probe()
>
>> +	device_for_each_child_node_scoped(dev, child) {
>> +		u32 reg;
>> +
>> +		if (fwnode_property_read_u32(child, "reg", &reg))
>> +			continue;
>> +
>> +		if (led[reg].regmap) {
>> +			dev_warn(dev, "duplicate node for channel %d\n", reg);
>> +			continue;
>> +		}
>
> If reg > MAX_CHANNELS, you just created an OOB condition.
>
>> +
>> +		led[reg].regmap = ddata->regmap_pmic;
>> +		led[reg].channel = (u8)reg;
>> +
>> +		ret = devm_mutex_init(dev, &led[reg].lock);
>> +		if (ret)
>> +			return dev_err_probe(dev, ret, "failed to create mutex\n");
>> +
>> +		switch (platform_get_device_id(pdev)->driver_data) {
>> +		case S2MU005:
>> +			ret = s2mu005_fled_init_channel(led + reg, dev, child);
>> +			if (ret < 0)
>> +				return ret;
>> +			break;
>> +		}
>
> This is even more odd!

What's exactly odd about it?

>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct platform_device_id s2m_fled_id_table[] = {
>> +	{ "s2mu005-flash", S2MU005 },
>> +	{ /* sentinel */ },
>> +};
>> +MODULE_DEVICE_TABLE(platform, s2m_fled_id_table);
>> +
>> +static const struct of_device_id s2m_fled_of_match_table[] = {
>> +	{ .compatible = "samsung,s2mu005-flash", .data = (void *)S2MU005 },
>> +	{ /* sentinel */ },
>> +};
>> +MODULE_DEVICE_TABLE(of, s2m_fled_of_match_table);
>> +
>> +static struct platform_driver s2m_fled_driver = {
>> +	.driver = {
>> +		.name = "s2m-flash",
>> +	},
>> +	.probe = s2m_fled_probe,
>> +	.id_table = s2m_fled_id_table,
>> +};
>> +module_platform_driver(s2m_fled_driver);
>> +
>> +MODULE_DESCRIPTION("Flash/Torch LED Driver For Samsung S2M Series PMICs");
>> +MODULE_AUTHOR("Kaustabh Chakraborty <kauschluss@disroot.org>");
>> +MODULE_LICENSE("GPL");


^ permalink raw reply

* Re: [PATCH v17 02/11] cxl/ras: Unify Endpoint and Port AER trace events
From: Bowman, Terry @ 2026-05-07 18:33 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: dave, dave.jiang, alison.schofield, djbw, bhelgaas, shiju.jose,
	ming.li, Smita.KoralahalliChannabasappa, rrichter, dan.carpenter,
	PradeepVineshReddy.Kodamati, lukas, Benjamin.Cheatham,
	sathyanarayanan.kuppuswamy, vishal.l.verma, alucerop, ira.weiny,
	corbet, rafael, xueshuai, linux-cxl, linux-kernel, linux-pci,
	linux-acpi, linux-doc, Mauro Carvalho Chehab
In-Reply-To: <20260507190836.70197e24@jic23-huawei>

On 5/7/2026 1:08 PM, Jonathan Cameron wrote:
> [Some people who received this message don't often get email from jic23@kernel.org. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
> 
> On Tue, 5 May 2026 12:30:20 -0500
> Terry Bowman <terry.bowman@amd.com> wrote:
> 
>> From: Dan Williams <djbw@kernel.org>
> 
> +CC Mauro - rasdaemon related - see below.
> 
>>
>> CXL protocol error logging uses two parallel sets of trace events. The
>> cxl_port_aer_correctable_error() and cxl_port_aer_uncorrectable_error()
>> events are used by CPER for CXL Port devices. The cxl_aer_correctable_error()
>> and cxl_aer_uncorrectable_error() events are used for CXL Endpoints. Update
>> the trace routines to use the latter for all CXL devices on both the CPER
>> and native AER paths.
>>
>> Generalize cxl_aer_correctable_error()/cxl_aer_uncorrectable_error to
>> take a struct device * and a u64 serial argument supplied by the caller.
>> cxl_handle_ras() and cxl_handle_cor_ras() gain the new u64 serial parameter,
>> sourced from pci_get_dsn().
>>
>> The CPER path keeps its existing Port-vs-Endpoint dispatch and passes the
>> new arguments to the unified trace events. The CPER path will be folded
>> together in a following patch.
>>
>> Remove the now-unused cxl_port_aer_correctable_error() and
>> cxl_port_aer_uncorrectable_error().
>>
>> **WARNING: ABI BREAK**
>> Rename the trace event field "memdev" to "device" so all CXL device types
>> (Ports and Endpoints) can be reported under a common field name. Note this
>> is an ABI break for userspace tools that key off the old "memdev" field.
>> Specifically, rasdaemon's ras-cxl-handler.c looks up "memdev" and bails on
>> NULL, so an unmodified rasdaemon will drop every CXL CE/UCE event once this
>> kernel ships. A rasdaemon update is needed in a separate series.
>>
>> The need for the field rename was discussed in v16 review [1].
> 
> This concerns me (sorry I wasn't paying attention to the v16 thread).
> It is a userspace regression against code that is out in the wild and typically
> not updated in sync with the kernel.
> 
> If you are suggesting breaking ras-daemon at the very least +CC the maintainer.
> 
> To get to a unified tracepoint add a new one that does what you want, but
> maintain the existing ones as well.  Userspace can then migrate and maybe
> in 5+ years time we can delete the non unified ones.
> 
> No actually comments on the code, just left it all here for Mauro,
> 
> Thanks,
> 
> Jonathan
> 

Dan was clear about using a single set of CE and UE handlers for all CXL RAS 
protocol errors. While I understand there may be concerns, please direct any 
objections to Dan and clarify what changes are required to avoid this 
repeatedly going back and forth.

[1] https://lore.kernel.org/linux-cxl/69cb2d5ba3111_178904100b7@dwillia2-mobl4.notmuch/

- Terry


>>
>> Also, for CXL Upstream Switch Port (USP) and Endpoint (EP) fatal UCE,
>> the cxl_aer_uncorrectable_error trace event is not emitted. The AER core
>> only retrieves PCI_ERR_UNCOR_STATUS for Root Ports, RCECs, and Downstream
>> Ports, or for non-fatal severities. PCI config reads to the source device
>> are expected to fail otherwise, so the AER core never reads the status
>> word, is_cxl_error() does not classify the event as CXL, and the AER path
>> handles it instead. In this case the AER handler consumes the event and
>> logs it as an AER error without calling the CXL RAS handlers or trace
>> logging.
>>
>> Before this patch, Endpoint and Port devices emitted different events:
>>
>>   # Endpoint (cxl_aer_*):
>>   cxl_aer_correctable_error: memdev=mem0 host=0000:0c:00.0 serial=0: status: 'CRC Threshold Hit'
>>   cxl_aer_uncorrectable_error: memdev=mem0 host=0000:0c:00.0 serial=0: status: 'Cache Data ECC Error | Memory Data ECC Error' first_error: 'Cache Data ECC Error'
>>
>>   # Port (cxl_port_aer_*, no serial field):
>>   cxl_port_aer_correctable_error: device=0000:0c:00.0 host=pci0000:0c status='CRC Threshold Hit'
>>   cxl_port_aer_uncorrectable_error: device=0000:0c:00.0 host=pci0000:0c status: 'Cache Data ECC Error | Memory Data ECC Error' first_error: 'Cache Data ECC Error'
>>
>> After this patch, all CXL devices emit the unified cxl_aer_* events
>> with the same field layout:
>>
>>   cxl_aer_correctable_error: device=0000:0c:00.0 host=pci0000:0c serial=0 status: 'CRC Threshold Hit'
>>   cxl_aer_uncorrectable_error: device=0000:0c:00.0 host=pci0000:0c serial=0 status: 'Cache Data ECC Error | Memory Data ECC Error' first_error: 'Cache Data ECC Error'
>>
>> [1] https://lore.kernel.org/linux-cxl/69cb2d5ba3111_178904100b7@dwillia2-mobl4.notmuch/
>>
>> Co-developed-by: Terry Bowman <terry.bowman@amd.com>
>> Signed-off-by: Terry Bowman <terry.bowman@amd.com>
>> Signed-off-by: Dan Williams <djbw@kernel.org>
>>
>> ---
>>
>> Changes in v16->v17:
>> - Replace cxlds->serial with pci_get_dsn()
>> - Change 'memdev' to 'device' (Dan)
>> - Updated Commit message
>>
>> Changes in v15->v16:
>> - Add Dan's review-by
>> - Incorporate Dan's comment into commit message:
>> "Add the serial number at the end to preserve compatibility with
>> libtraceevent parsing of the parameters."
>>
>> Changes in v14->v15:
>> - Update commit message.
>> - Moved cxl_handle_ras/cxl_handle_cor_ras() changes to future patch (terry)
>>
>> Changes in v13->v14:
>> - Update commit headline (Bjorn)
>>
>> Changes in v12->v13:
>> - Added Dave Jiang's review-by
>>
>> Changes in v11 -> v12:
>> - Correct parameters to call trace_cxl_aer_correctable_error()
>> - Add reviewed-by for Jonathan and Shiju
>>
>> Changes in v10->v11:
>> - Updated CE and UCE trace routines to maintain consistent TP_Struct ABI
>> and unchanged TP_printk() logging.
>> ---
>>  drivers/cxl/core/core.h    | 11 ++++--
>>  drivers/cxl/core/ras.c     | 39 +++++++++++--------
>>  drivers/cxl/core/ras_rch.c |  6 ++-
>>  drivers/cxl/core/trace.h   | 76 ++++++++------------------------------
>>  4 files changed, 49 insertions(+), 83 deletions(-)
>>
>> diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h
>> index 82ca3a476708..132ac9c1ebf4 100644
>> --- a/drivers/cxl/core/core.h
>> +++ b/drivers/cxl/core/core.h
>> @@ -183,8 +183,9 @@ static inline struct device *dport_to_host(struct cxl_dport *dport)
>>  #ifdef CONFIG_CXL_RAS
>>  int cxl_ras_init(void);
>>  void cxl_ras_exit(void);
>> -bool cxl_handle_ras(struct device *dev, void __iomem *ras_base);
>> -void cxl_handle_cor_ras(struct device *dev, void __iomem *ras_base);
>> +bool cxl_handle_ras(struct device *dev, u64 serial, void __iomem *ras_base);
>> +void cxl_handle_cor_ras(struct device *dev, u64 serial,
>> +                     void __iomem *ras_base);
>>  void cxl_dport_map_rch_aer(struct cxl_dport *dport);
>>  void cxl_disable_rch_root_ints(struct cxl_dport *dport);
>>  void cxl_handle_rdport_errors(struct cxl_dev_state *cxlds);
>> @@ -195,11 +196,13 @@ static inline int cxl_ras_init(void)
>>       return 0;
>>  }
>>  static inline void cxl_ras_exit(void) { }
>> -static inline bool cxl_handle_ras(struct device *dev, void __iomem *ras_base)
>> +static inline bool cxl_handle_ras(struct device *dev, u64 serial,
>> +                               void __iomem *ras_base)
>>  {
>>       return false;
>>  }
>> -static inline void cxl_handle_cor_ras(struct device *dev, void __iomem *ras_base) { }
>> +static inline void cxl_handle_cor_ras(struct device *dev, u64 serial,
>> +                                   void __iomem *ras_base) { }
>>  static inline void cxl_dport_map_rch_aer(struct cxl_dport *dport) { }
>>  static inline void cxl_disable_rch_root_ints(struct cxl_dport *dport) { }
>>  static inline void cxl_handle_rdport_errors(struct cxl_dev_state *cxlds) { }
>> diff --git a/drivers/cxl/core/ras.c b/drivers/cxl/core/ras.c
>> index 006c6ffc2f56..d7081caaf5d3 100644
>> --- a/drivers/cxl/core/ras.c
>> +++ b/drivers/cxl/core/ras.c
>> @@ -13,7 +13,7 @@ static void cxl_cper_trace_corr_port_prot_err(struct pci_dev *pdev,
>>  {
>>       u32 status = ras_cap.cor_status & ~ras_cap.cor_mask;
>>
>> -     trace_cxl_port_aer_correctable_error(&pdev->dev, status);
>> +     trace_cxl_aer_correctable_error(&pdev->dev, status, pci_get_dsn(pdev));
>>  }
>>
>>  static void cxl_cper_trace_uncorr_port_prot_err(struct pci_dev *pdev,
>> @@ -28,20 +28,24 @@ static void cxl_cper_trace_uncorr_port_prot_err(struct pci_dev *pdev,
>>       else
>>               fe = status;
>>
>> -     trace_cxl_port_aer_uncorrectable_error(&pdev->dev, status, fe,
>> -                                            ras_cap.header_log);
>> +     trace_cxl_aer_uncorrectable_error(&pdev->dev, status, fe,
>> +                                       ras_cap.header_log,
>> +                                       pci_get_dsn(pdev));
>>  }
>>
>> -static void cxl_cper_trace_corr_prot_err(struct cxl_memdev *cxlmd,
>> +static void cxl_cper_trace_corr_prot_err(struct pci_dev *pdev,
>> +                                      struct cxl_memdev *cxlmd,
>>                                        struct cxl_ras_capability_regs ras_cap)
>>  {
>>       u32 status = ras_cap.cor_status & ~ras_cap.cor_mask;
>>
>> -     trace_cxl_aer_correctable_error(cxlmd, status);
>> +     trace_cxl_aer_correctable_error(&cxlmd->dev, status,
>> +                                     pci_get_dsn(pdev));
>>  }
>>
>>  static void
>> -cxl_cper_trace_uncorr_prot_err(struct cxl_memdev *cxlmd,
>> +cxl_cper_trace_uncorr_prot_err(struct pci_dev *pdev,
>> +                            struct cxl_memdev *cxlmd,
>>                              struct cxl_ras_capability_regs ras_cap)
>>  {
>>       u32 status = ras_cap.uncor_status & ~ras_cap.uncor_mask;
>> @@ -53,8 +57,9 @@ cxl_cper_trace_uncorr_prot_err(struct cxl_memdev *cxlmd,
>>       else
>>               fe = status;
>>
>> -     trace_cxl_aer_uncorrectable_error(cxlmd, status, fe,
>> -                                       ras_cap.header_log);
>> +     trace_cxl_aer_uncorrectable_error(&cxlmd->dev, status, fe,
>> +                                       ras_cap.header_log,
>> +                                       pci_get_dsn(pdev));
>>  }
>>
>>  static int match_memdev_by_parent(struct device *dev, const void *uport)
>> @@ -101,9 +106,9 @@ void cxl_cper_handle_prot_err(struct cxl_cper_prot_err_work_data *data)
>>
>>       cxlmd = to_cxl_memdev(mem_dev);
>>       if (data->severity == AER_CORRECTABLE)
>> -             cxl_cper_trace_corr_prot_err(cxlmd, data->ras_cap);
>> +             cxl_cper_trace_corr_prot_err(pdev, cxlmd, data->ras_cap);
>>       else
>> -             cxl_cper_trace_uncorr_prot_err(cxlmd, data->ras_cap);
>> +             cxl_cper_trace_uncorr_prot_err(pdev, cxlmd, data->ras_cap);
>>  }
>>  EXPORT_SYMBOL_GPL(cxl_cper_handle_prot_err);
>>
>> @@ -183,7 +188,7 @@ void devm_cxl_port_ras_setup(struct cxl_port *port)
>>  }
>>  EXPORT_SYMBOL_NS_GPL(devm_cxl_port_ras_setup, "CXL");
>>
>> -void cxl_handle_cor_ras(struct device *dev, void __iomem *ras_base)
>> +void cxl_handle_cor_ras(struct device *dev, u64 serial, void __iomem *ras_base)
>>  {
>>       void __iomem *addr;
>>       u32 status;
>> @@ -195,7 +200,7 @@ void cxl_handle_cor_ras(struct device *dev, void __iomem *ras_base)
>>       status = readl(addr);
>>       if (status & CXL_RAS_CORRECTABLE_STATUS_MASK) {
>>               writel(status & CXL_RAS_CORRECTABLE_STATUS_MASK, addr);
>> -             trace_cxl_aer_correctable_error(to_cxl_memdev(dev), status);
>> +             trace_cxl_aer_correctable_error(dev, status, serial);
>>       }
>>  }
>>
>> @@ -220,7 +225,7 @@ static void header_log_copy(void __iomem *ras_base, u32 *log)
>>   * Log the state of the RAS status registers and prepare them to log the
>>   * next error status. Return 1 if reset needed.
>>   */
>> -bool cxl_handle_ras(struct device *dev, void __iomem *ras_base)
>> +bool cxl_handle_ras(struct device *dev, u64 serial, void __iomem *ras_base)
>>  {
>>       u32 hl[CXL_HEADERLOG_SIZE_U32];
>>       void __iomem *addr;
>> @@ -247,7 +252,7 @@ bool cxl_handle_ras(struct device *dev, void __iomem *ras_base)
>>       }
>>
>>       header_log_copy(ras_base, hl);
>> -     trace_cxl_aer_uncorrectable_error(to_cxl_memdev(dev), status, fe, hl);
>> +     trace_cxl_aer_uncorrectable_error(dev, status, fe, hl, serial);
>>       writel(status & CXL_RAS_UNCORRECTABLE_STATUS_MASK, addr);
>>
>>       return true;
>> @@ -270,7 +275,8 @@ void cxl_cor_error_detected(struct pci_dev *pdev)
>>               if (cxlds->rcd)
>>                       cxl_handle_rdport_errors(cxlds);
>>
>> -             cxl_handle_cor_ras(&cxlds->cxlmd->dev, cxlmd->endpoint->regs.ras);
>> +             cxl_handle_cor_ras(&cxlds->cxlmd->dev, pci_get_dsn(pdev),
>> +                                cxlmd->endpoint->regs.ras);
>>       }
>>  }
>>  EXPORT_SYMBOL_NS_GPL(cxl_cor_error_detected, "CXL");
>> @@ -299,7 +305,8 @@ pci_ers_result_t cxl_error_detected(struct pci_dev *pdev,
>>                * chance the situation is recoverable dump the status of the RAS
>>                * capability registers and bounce the active state of the memdev.
>>                */
>> -             ue = cxl_handle_ras(&cxlds->cxlmd->dev, cxlmd->endpoint->regs.ras);
>> +             ue = cxl_handle_ras(&cxlds->cxlmd->dev, pci_get_dsn(pdev),
>> +                                 cxlmd->endpoint->regs.ras);
>>       }
>>
>>       switch (state) {
>> diff --git a/drivers/cxl/core/ras_rch.c b/drivers/cxl/core/ras_rch.c
>> index 0a8b3b9b6388..61835fbafc0f 100644
>> --- a/drivers/cxl/core/ras_rch.c
>> +++ b/drivers/cxl/core/ras_rch.c
>> @@ -115,7 +115,9 @@ void cxl_handle_rdport_errors(struct cxl_dev_state *cxlds)
>>
>>       pci_print_aer(pdev, severity, &aer_regs);
>>       if (severity == AER_CORRECTABLE)
>> -             cxl_handle_cor_ras(&cxlds->cxlmd->dev, dport->regs.ras);
>> +             cxl_handle_cor_ras(&cxlds->cxlmd->dev, pci_get_dsn(pdev),
>> +                                dport->regs.ras);
>>       else
>> -             cxl_handle_ras(&cxlds->cxlmd->dev, dport->regs.ras);
>> +             cxl_handle_ras(&cxlds->cxlmd->dev, pci_get_dsn(pdev),
>> +                            dport->regs.ras);
>>  }
>> diff --git a/drivers/cxl/core/trace.h b/drivers/cxl/core/trace.h
>> index a972e4ef1936..6f3957b3c3af 100644
>> --- a/drivers/cxl/core/trace.h
>> +++ b/drivers/cxl/core/trace.h
>> @@ -48,49 +48,22 @@
>>       { CXL_RAS_UC_IDE_RX_ERR, "IDE Rx Error" }                         \
>>  )
>>
>> -TRACE_EVENT(cxl_port_aer_uncorrectable_error,
>> -     TP_PROTO(struct device *dev, u32 status, u32 fe, u32 *hl),
>> -     TP_ARGS(dev, status, fe, hl),
>> +TRACE_EVENT(cxl_aer_uncorrectable_error,
>> +     TP_PROTO(const struct device *dev, u32 status, u32 fe, u32 *hl,
>> +              u64 serial),
>> +     TP_ARGS(dev, status, fe, hl, serial),
>>       TP_STRUCT__entry(
>>               __string(device, dev_name(dev))
>>               __string(host, dev_name(dev->parent))
>> -             __field(u32, status)
>> -             __field(u32, first_error)
>> -             __array(u32, header_log, CXL_HEADERLOG_SIZE_U32)
>> -     ),
>> -     TP_fast_assign(
>> -             __assign_str(device);
>> -             __assign_str(host);
>> -             __entry->status = status;
>> -             __entry->first_error = fe;
>> -             /*
>> -              * Embed the 512B headerlog data for user app retrieval and
>> -              * parsing, but no need to print this in the trace buffer.
>> -              */
>> -             memcpy(__entry->header_log, hl, CXL_HEADERLOG_SIZE);
>> -     ),
>> -     TP_printk("device=%s host=%s status: '%s' first_error: '%s'",
>> -               __get_str(device), __get_str(host),
>> -               show_uc_errs(__entry->status),
>> -               show_uc_errs(__entry->first_error)
>> -     )
>> -);
>> -
>> -TRACE_EVENT(cxl_aer_uncorrectable_error,
>> -     TP_PROTO(const struct cxl_memdev *cxlmd, u32 status, u32 fe, u32 *hl),
>> -     TP_ARGS(cxlmd, status, fe, hl),
>> -     TP_STRUCT__entry(
>> -             __string(memdev, dev_name(&cxlmd->dev))
>> -             __string(host, dev_name(cxlmd->dev.parent))
>>               __field(u64, serial)
>>               __field(u32, status)
>>               __field(u32, first_error)
>>               __array(u32, header_log, CXL_HEADERLOG_SIZE_U32)
>>       ),
>>       TP_fast_assign(
>> -             __assign_str(memdev);
>> +             __assign_str(device);
>>               __assign_str(host);
>> -             __entry->serial = cxlmd->cxlds->serial;
>> +             __entry->serial = serial;
>>               __entry->status = status;
>>               __entry->first_error = fe;
>>               /*
>> @@ -99,8 +72,8 @@ TRACE_EVENT(cxl_aer_uncorrectable_error,
>>                */
>>               memcpy(__entry->header_log, hl, CXL_HEADERLOG_SIZE);
>>       ),
>> -     TP_printk("memdev=%s host=%s serial=%lld: status: '%s' first_error: '%s'",
>> -               __get_str(memdev), __get_str(host), __entry->serial,
>> +     TP_printk("device=%s host=%s serial=%lld status: '%s' first_error: '%s'",
>> +               __get_str(device), __get_str(host), __entry->serial,
>>                 show_uc_errs(__entry->status),
>>                 show_uc_errs(__entry->first_error)
>>       )
>> @@ -124,42 +97,23 @@ TRACE_EVENT(cxl_aer_uncorrectable_error,
>>       { CXL_RAS_CE_PHYS_LAYER_ERR, "Received Error From Physical Layer" }     \
>>  )
>>
>> -TRACE_EVENT(cxl_port_aer_correctable_error,
>> -     TP_PROTO(struct device *dev, u32 status),
>> -     TP_ARGS(dev, status),
>> +TRACE_EVENT(cxl_aer_correctable_error,
>> +     TP_PROTO(const struct device *dev, u32 status, u64 serial),
>> +     TP_ARGS(dev, status, serial),
>>       TP_STRUCT__entry(
>>               __string(device, dev_name(dev))
>>               __string(host, dev_name(dev->parent))
>> -             __field(u32, status)
>> -     ),
>> -     TP_fast_assign(
>> -             __assign_str(device);
>> -             __assign_str(host);
>> -             __entry->status = status;
>> -     ),
>> -     TP_printk("device=%s host=%s status='%s'",
>> -               __get_str(device), __get_str(host),
>> -               show_ce_errs(__entry->status)
>> -     )
>> -);
>> -
>> -TRACE_EVENT(cxl_aer_correctable_error,
>> -     TP_PROTO(const struct cxl_memdev *cxlmd, u32 status),
>> -     TP_ARGS(cxlmd, status),
>> -     TP_STRUCT__entry(
>> -             __string(memdev, dev_name(&cxlmd->dev))
>> -             __string(host, dev_name(cxlmd->dev.parent))
>>               __field(u64, serial)
>>               __field(u32, status)
>>       ),
>>       TP_fast_assign(
>> -             __assign_str(memdev);
>> +             __assign_str(device);
>>               __assign_str(host);
>> -             __entry->serial = cxlmd->cxlds->serial;
>> +             __entry->serial = serial;
>>               __entry->status = status;
>>       ),
>> -     TP_printk("memdev=%s host=%s serial=%lld: status: '%s'",
>> -               __get_str(memdev), __get_str(host), __entry->serial,
>> +     TP_printk("device=%s host=%s serial=%lld status: '%s'",
>> +               __get_str(device), __get_str(host), __entry->serial,
>>                 show_ce_errs(__entry->status)
>>       )
>>  );
> 


^ permalink raw reply

* Re: [PATCH] Documentation: fix typo and formattting in security/credentials.rst
From: Jonathan Corbet @ 2026-05-07 18:31 UTC (permalink / raw)
  To: John Doe, Randy Dunlap
  Cc: Paul Moore, Serge Hallyn, Shuah Khan, linux-security-module,
	linux-doc, linux-kernel
In-Reply-To: <afzQVIv4-Up24VNM@gmail.com>

John Doe <drapl0n.kernel@gmail.com> writes:

> Thank you for the feedback and acknowledgment. I thought that since my
> earlier patch was invalid/corrupted, I should submit a new one rather than
> version 2.
>
> I understand the procedure now and will ensure that future revisions follow
> the guidelines.

Along those lines, seeing multiple names associated with the same email
account is a bit disconcerting.  We are not generally interested in
taking patches submitted under pseudonyms...

jon

^ permalink raw reply

* Re: [PATCH RFC v5 01/53] KVM: guest_memfd: Introduce per-gmem attributes, use to guard user mappings
From: Ackerley Tng @ 2026-05-07 18:31 UTC (permalink / raw)
  To: Liam R. Howlett
  Cc: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
	ira.weiny, jmattson, jthoughton, michael.roth, oupton,
	pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
	steven.price, tabba, willy, wyihan, yan.y.zhao, forkloop,
	pratyush, suzuki.poulose, aneesh.kumar, Paolo Bonzini,
	Sean Christopherson, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, Steven Rostedt,
	Masami Hiramatsu, Mathieu Desnoyers, Jonathan Corbet, Shuah Khan,
	Shuah Khan, Vishal Annapurve, Andrew Morton, Chris Li,
	Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He, Barry Song,
	Axel Rasmussen, Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng,
	Shakeel Butt, Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka,
	kvm, linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
	linux-mm, linux-coco
In-Reply-To: <byywwfin2aenobdwuesqihm6nzxyx6ecedwgbt7f5tvaaul6fi@u7bmexpavwdn>

"Liam R. Howlett" <liam@infradead.org> writes:

>
> [...snip...]
>
>> +static u64 kvm_gmem_get_attributes(struct inode *inode, pgoff_t index)
>> +{
>> +	struct maple_tree *mt = &GMEM_I(inode)->attributes;
>> +	void *entry = mtree_load(mt, index);
>> +
>> +	/*
>> +	 * The lock _must_ be held for lookups, as some maple tree operations,
>> +	 * e.g. append, are unsafe (return inaccurate information) with respect
>> +	 * to concurrent RCU-protected lookups.
>> +	 */
>
> Can you please elaborate how you see inaccurate information and which
> information is inaccurate?
>
> Your comment is incorrect and misleading as append will not be used in
> rcu mode.  Note that you have not set this tree up in rcu mode.
>

My bad. Thanks for clarifying about usage of rcu mode.

>> +	lockdep_assert(mt_lock_is_held(mt));
>> +

In the next revision I'll remove this lockdep and use RCU mode, and
kvm_gmem_get_memory_attributes() should get a stable result.

The other lookups using mt_for_each() in kvm_gmem_range_has_attributes()
and kvm_gmem_get_invalidate_filter() retain the lockdep since those
operate over multiple ranges. Those are called from paths that require
holding the lock to exclude other operations anyway, so the lockdep
requirement does not cost anything more.

>> +	return WARN_ON_ONCE(!entry) ? 0 : xa_to_value(entry);
>> +}
>> +
>>
>> [...snip...]
>>

^ permalink raw reply


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