From 32782e3115aa892f7e67cc8254808327f77217c9 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Tue, 26 May 2026 14:39:07 -0400 Subject: [PATCH 001/135] rust: binder: use strict provenance APIs Replace the pointer-to-integer conversions in the Binder Rust driver with calls to the strict provenance APIs. The strict provenance APIs were stabilized in Rust 1.84.0 [1]. Since commit f32fb9c58a5b ("rust: bump Rust minimum supported version to 1.85.0 (Debian Trixie)"), the minimum supported Rust version is 1.85.0, so no polyfills are needed. Link: https://blog.rust-lang.org/2025/01/09/Rust-1.84.0.html#strict-provenance-apis [1] Reviewed-by: Alice Ryhl Assisted-by: Codex:gpt-5 Signed-off-by: Tamir Duberstein Reviewed-by: Gary Guo Link: https://patch.msgid.link/20260526-binder-strict-provenance-v2-1-a41d89c29bc5@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/node.rs | 2 +- drivers/android/binder/rust_binder_main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/android/binder/node.rs b/drivers/android/binder/node.rs index 69f757ff7461..d710940c3c8f 100644 --- a/drivers/android/binder/node.rs +++ b/drivers/android/binder/node.rs @@ -321,7 +321,7 @@ impl Node { /// An id that is unique across all binder nodes on the system. Used as the key in the /// `by_node` map. pub(crate) fn global_id(&self) -> usize { - self as *const Node as usize + (self as *const Node).addr() } pub(crate) fn get_id(&self) -> (u64, u64) { diff --git a/drivers/android/binder/rust_binder_main.rs b/drivers/android/binder/rust_binder_main.rs index dc1941cd2407..d487638266e3 100644 --- a/drivers/android/binder/rust_binder_main.rs +++ b/drivers/android/binder/rust_binder_main.rs @@ -511,7 +511,7 @@ unsafe extern "C" fn rust_binder_proc_show( _: *mut kernel::ffi::c_void, ) -> kernel::ffi::c_int { // SAFETY: Accessing the private field of `seq_file` is okay. - let pid = (unsafe { (*ptr).private }) as usize as Pid; + let pid = unsafe { (*ptr).private }.addr() as Pid; // SAFETY: The caller ensures that the pointer is valid and exclusive for the duration in which // this method is called. let m = unsafe { SeqFile::from_raw(ptr) }; From 7d7b2011e7554d481a6db9cf362a58508b3e009e Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Tue, 26 May 2026 14:39:08 -0400 Subject: [PATCH 002/135] rust: binder: enable `clippy::ptr_as_ptr` lint In Rust 1.51.0, Clippy introduced the `ptr_as_ptr` lint [1]: > Though `as` casts between raw pointers are not terrible, > `pointer::cast` is safer because it cannot accidentally change pointer > mutability or cast the pointer to other types like `usize`. Apply the required changes and enable the lint in the Binder Rust driver -- no functional change intended. Link: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_as_ptr [1] Assisted-by: Codex:gpt-5 Signed-off-by: Tamir Duberstein Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20260526-binder-strict-provenance-v2-2-a41d89c29bc5@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/defs.rs | 4 ++-- drivers/android/binder/page_range.rs | 6 +++--- drivers/android/binder/rust_binder_main.rs | 7 +------ 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/drivers/android/binder/defs.rs b/drivers/android/binder/defs.rs index 33f51b4139c7..354c32c39e83 100644 --- a/drivers/android/binder/defs.rs +++ b/drivers/android/binder/defs.rs @@ -165,8 +165,8 @@ impl BinderTransactionDataSecctx { pub(crate) fn tr_data(&mut self) -> &mut BinderTransactionData { // SAFETY: Transparent wrapper is safe to transmute. unsafe { - &mut *(&mut self.transaction_data as *mut uapi::binder_transaction_data - as *mut BinderTransactionData) + &mut *((&mut self.transaction_data as *mut uapi::binder_transaction_data) + .cast::()) } } } diff --git a/drivers/android/binder/page_range.rs b/drivers/android/binder/page_range.rs index e82a5523804f..fbb93ebb9697 100644 --- a/drivers/android/binder/page_range.rs +++ b/drivers/android/binder/page_range.rs @@ -571,7 +571,7 @@ impl ShrinkablePageRange { unsafe { self.iterate(offset, size_of::(), |page, offset, to_copy| { // SAFETY: The sum of `offset` and `to_copy` is bounded by the size of T. - let obj_ptr = (out.as_mut_ptr() as *mut u8).add(out_offset); + let obj_ptr = out.as_mut_ptr().cast::().add(out_offset); // SAFETY: The pointer points is in-bounds of the `out` variable, so it is valid. page.read_raw(obj_ptr, offset, to_copy)?; out_offset += to_copy; @@ -593,7 +593,7 @@ impl ShrinkablePageRange { unsafe { self.iterate(offset, size_of_val(obj), |page, offset, to_copy| { // SAFETY: The sum of `offset` and `to_copy` is bounded by the size of T. - let obj_ptr = (obj as *const T as *const u8).add(obj_offset); + let obj_ptr = (obj as *const T).cast::().add(obj_offset); // SAFETY: We have a reference to the object, so the pointer is valid. page.write_raw(obj_ptr, offset, to_copy)?; obj_offset += to_copy; @@ -712,7 +712,7 @@ unsafe extern "C" fn rust_shrink_free_page( { // CAST: The `list_head` field is first in `PageInfo`. - let info = item as *mut PageInfo; + let info = item.cast::(); // SAFETY: The `range` field of `PageInfo` is immutable. range_ptr = unsafe { (*info).range }; // SAFETY: The `range` outlives its `PageInfo` values. diff --git a/drivers/android/binder/rust_binder_main.rs b/drivers/android/binder/rust_binder_main.rs index d487638266e3..fa28697982d3 100644 --- a/drivers/android/binder/rust_binder_main.rs +++ b/drivers/android/binder/rust_binder_main.rs @@ -6,12 +6,7 @@ #![crate_name = "rust_binder"] #![recursion_limit = "256"] -#![allow( - clippy::as_underscore, - clippy::ref_as_ptr, - clippy::ptr_as_ptr, - clippy::cast_lossless -)] +#![allow(clippy::as_underscore, clippy::ref_as_ptr, clippy::cast_lossless)] use kernel::{ bindings::{self, seq_file}, From 36bddf13dc1b88616a2ecdb331ffc23c42a3eb67 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Tue, 26 May 2026 14:39:09 -0400 Subject: [PATCH 003/135] rust: binder: enable `clippy::ref_as_ptr` lint In Rust 1.78.0, Clippy introduced the `ref_as_ptr` lint [1]: > Using `as` casts may result in silently changing mutability or type. While this does not eliminate unchecked `as` conversions, it makes such conversions easier to scrutinize. It also has the slight benefit of removing a degree of freedom on which to bikeshed. Thus apply the changes and enable the lint in the Binder Rust driver -- no functional change intended. Link: https://rust-lang.github.io/rust-clippy/master/index.html#ref_as_ptr [1] Assisted-by: Codex:gpt-5 Signed-off-by: Tamir Duberstein Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20260526-binder-strict-provenance-v2-3-a41d89c29bc5@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/defs.rs | 4 +++- drivers/android/binder/node.rs | 3 ++- drivers/android/binder/page_range.rs | 4 ++-- drivers/android/binder/rust_binder_main.rs | 2 +- drivers/android/binder/trace.rs | 4 +++- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/android/binder/defs.rs b/drivers/android/binder/defs.rs index 354c32c39e83..e697c2c13e29 100644 --- a/drivers/android/binder/defs.rs +++ b/drivers/android/binder/defs.rs @@ -4,6 +4,8 @@ use core::mem::MaybeUninit; use core::ops::{Deref, DerefMut}; +use core::ptr; + use kernel::{ transmute::{AsBytes, FromBytes}, uapi::{self, *}, @@ -165,7 +167,7 @@ impl BinderTransactionDataSecctx { pub(crate) fn tr_data(&mut self) -> &mut BinderTransactionData { // SAFETY: Transparent wrapper is safe to transmute. unsafe { - &mut *((&mut self.transaction_data as *mut uapi::binder_transaction_data) + &mut *(ptr::from_mut::(&mut self.transaction_data) .cast::()) } } diff --git a/drivers/android/binder/node.rs b/drivers/android/binder/node.rs index d710940c3c8f..3d96738dee66 100644 --- a/drivers/android/binder/node.rs +++ b/drivers/android/binder/node.rs @@ -21,6 +21,7 @@ use crate::{ }; use core::mem; +use core::ptr; mod wrapper; pub(crate) use self::wrapper::CritIncrWrapper; @@ -321,7 +322,7 @@ impl Node { /// An id that is unique across all binder nodes on the system. Used as the key in the /// `by_node` map. pub(crate) fn global_id(&self) -> usize { - (self as *const Node).addr() + ptr::from_ref(self).addr() } pub(crate) fn get_id(&self) -> (u64, u64) { diff --git a/drivers/android/binder/page_range.rs b/drivers/android/binder/page_range.rs index fbb93ebb9697..52ffbf3504e7 100644 --- a/drivers/android/binder/page_range.rs +++ b/drivers/android/binder/page_range.rs @@ -312,7 +312,7 @@ impl ShrinkablePageRange { // SAFETY: This just initializes the pages array. unsafe { - let self_ptr = self as *const ShrinkablePageRange; + let self_ptr = ptr::from_ref(self); for i in 0..num_pages { let info = pages.as_mut_ptr().add(i); (&raw mut (*info).range).write(self_ptr); @@ -593,7 +593,7 @@ impl ShrinkablePageRange { unsafe { self.iterate(offset, size_of_val(obj), |page, offset, to_copy| { // SAFETY: The sum of `offset` and `to_copy` is bounded by the size of T. - let obj_ptr = (obj as *const T).cast::().add(obj_offset); + let obj_ptr = ptr::from_ref(obj).cast::().add(obj_offset); // SAFETY: We have a reference to the object, so the pointer is valid. page.write_raw(obj_ptr, offset, to_copy)?; obj_offset += to_copy; diff --git a/drivers/android/binder/rust_binder_main.rs b/drivers/android/binder/rust_binder_main.rs index fa28697982d3..88da29413e16 100644 --- a/drivers/android/binder/rust_binder_main.rs +++ b/drivers/android/binder/rust_binder_main.rs @@ -6,7 +6,7 @@ #![crate_name = "rust_binder"] #![recursion_limit = "256"] -#![allow(clippy::as_underscore, clippy::ref_as_ptr, clippy::cast_lossless)] +#![allow(clippy::as_underscore, clippy::cast_lossless)] use kernel::{ bindings::{self, seq_file}, diff --git a/drivers/android/binder/trace.rs b/drivers/android/binder/trace.rs index 5539672d7285..06aabb3cc2f1 100644 --- a/drivers/android/binder/trace.rs +++ b/drivers/android/binder/trace.rs @@ -4,6 +4,8 @@ use crate::transaction::Transaction; +use core::ptr; + use kernel::bindings::{rust_binder_transaction, task_struct}; use kernel::error::Result; use kernel::ffi::{c_int, c_uint, c_ulong}; @@ -26,7 +28,7 @@ declare_trace! { #[inline] fn raw_transaction(t: &Transaction) -> rust_binder_transaction { - t as *const Transaction as rust_binder_transaction + ptr::from_ref(t).cast_mut().cast() } #[inline] From e9217e9776812aa63ca428d04053f6239e4308a5 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Tue, 26 May 2026 14:39:10 -0400 Subject: [PATCH 004/135] rust: binder: enable `clippy::as_underscore` In Rust 1.63.0, Clippy introduced the `as_underscore` lint [1]: > The conversion might include lossy conversion or a dangerous cast that > might go undetected due to the type being inferred. > > The lint is allowed by default as using `_` is less wordy than always > specifying the type. Always specifying the type is especially helpful in function call contexts where the inferred type may change at a distance. Specifying the type also allows Clippy to spot more cases of `useless_conversion`. Several inferred conversions from `binder_uintptr_t` to the driver's internal `u64` node identifiers are identity conversions. Although the UAPI header retains `BINDER_IPC_32BIT` for userspace building against older kernels, commit 1190b4e38f97 ("ANDROID: binder: remove 32-bit binder interface.") removed kernel support for selecting that protocol. Rust Binder therefore uses the 64-bit Binder protocol on every supported architecture. While this does not eliminate unchecked `as` conversions, it makes such conversions easier to scrutinize. It also has the slight benefit of removing a degree of freedom on which to bikeshed. Thus apply the changes and enable the lint in the Binder Rust driver -- no functional change intended. Link: https://rust-lang.github.io/rust-clippy/master/index.html#as_underscore [1] Assisted-by: Codex:gpt-5 Signed-off-by: Tamir Duberstein Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20260526-binder-strict-provenance-v2-4-a41d89c29bc5@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/allocation.rs | 4 ++-- drivers/android/binder/defs.rs | 2 +- drivers/android/binder/node.rs | 11 +++++----- drivers/android/binder/node/wrapper.rs | 2 +- drivers/android/binder/process.rs | 8 ++++---- drivers/android/binder/rust_binder_main.rs | 4 ++-- drivers/android/binder/thread.rs | 24 ++++++++++++---------- drivers/android/binder/transaction.rs | 14 +++++++------ 8 files changed, 37 insertions(+), 32 deletions(-) diff --git a/drivers/android/binder/allocation.rs b/drivers/android/binder/allocation.rs index b7b05e72970a..d411a16bc63f 100644 --- a/drivers/android/binder/allocation.rs +++ b/drivers/android/binder/allocation.rs @@ -384,8 +384,8 @@ impl<'a> AllocationView<'a> { BINDER_TYPE_WEAK_BINDER }; newobj.flags = obj.flags; - newobj.__bindgen_anon_1.binder = ptr as _; - newobj.cookie = cookie as _; + newobj.__bindgen_anon_1.binder = ptr as uapi::binder_uintptr_t; + newobj.cookie = cookie as uapi::binder_uintptr_t; self.write(offset, &newobj)?; // Increment the user ref count on the node. It will be decremented as part of the // destruction of the buffer, when we see a binder or weak-binder object. diff --git a/drivers/android/binder/defs.rs b/drivers/android/binder/defs.rs index e697c2c13e29..8ac9bdd7a499 100644 --- a/drivers/android/binder/defs.rs +++ b/drivers/android/binder/defs.rs @@ -148,7 +148,7 @@ decl_wrapper!(ExtendedError, uapi::binder_extended_error); impl BinderVersion { pub(crate) fn current() -> Self { Self(MaybeUninit::new(uapi::binder_version { - protocol_version: BINDER_CURRENT_PROTOCOL_VERSION as _, + protocol_version: BINDER_CURRENT_PROTOCOL_VERSION as i32, })) } } diff --git a/drivers/android/binder/node.rs b/drivers/android/binder/node.rs index 3d96738dee66..4e75f58bf0db 100644 --- a/drivers/android/binder/node.rs +++ b/drivers/android/binder/node.rs @@ -9,6 +9,7 @@ use kernel::{ seq_print, sync::lock::{spinlock::SpinLockBackend, Guard}, sync::{Arc, LockedBy, SpinLock}, + uapi, }; use crate::{ @@ -465,7 +466,7 @@ impl Node { owner_inner: &mut ProcessInner, ) -> Option> { match self.incr_refcount_allow_zero2one(strong, owner_inner) { - Ok(Some(node)) => Some(node as _), + Ok(Some(node)) => Some(node as DLArc), Ok(None) => None, Err(CouldNotDeliverCriticalIncrement) => { assert!(strong); @@ -490,8 +491,8 @@ impl Node { guard: &Guard<'_, ProcessInner, SpinLockBackend>, ) { let inner = self.inner.access(guard); - out.strong_count = inner.strong.count as _; - out.weak_count = inner.weak.count as _; + out.strong_count = inner.strong.count as u32; + out.weak_count = inner.weak.count as u32; } pub(crate) fn populate_debug_info( @@ -499,8 +500,8 @@ impl Node { out: &mut BinderNodeDebugInfo, guard: &Guard<'_, ProcessInner, SpinLockBackend>, ) { - out.ptr = self.ptr as _; - out.cookie = self.cookie as _; + out.ptr = self.ptr as uapi::binder_uintptr_t; + out.cookie = self.cookie as uapi::binder_uintptr_t; let inner = self.inner.access(guard); if inner.strong.has_count { out.has_strong_ref = 1; diff --git a/drivers/android/binder/node/wrapper.rs b/drivers/android/binder/node/wrapper.rs index 43294c050502..6e4ca01c941a 100644 --- a/drivers/android/binder/node/wrapper.rs +++ b/drivers/android/binder/node/wrapper.rs @@ -21,7 +21,7 @@ impl CritIncrWrapper { pub(super) fn init(self, node: DArc) -> DLArc { match self.inner.pin_init_with(DTRWrap::new(NodeWrapper { node })) { - Ok(initialized) => ListArc::from(initialized) as _, + Ok(initialized) => ListArc::from(initialized) as DLArc, Err(err) => match err {}, } } diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs index 96b8440ceac6..49ca1bcd21a1 100644 --- a/drivers/android/binder/process.rs +++ b/drivers/android/binder/process.rs @@ -32,7 +32,7 @@ use kernel::{ lock::{spinlock::SpinLockBackend, Guard}, Arc, ArcBorrow, CondVar, CondVarTimeoutResult, Mutex, SpinLock, UniqueArc, }, - task::Task, + task::{Pid, Task}, uaccess::{UserSlice, UserSliceReader}, uapi, workqueue::{self, Work}, @@ -259,7 +259,7 @@ impl ProcessInner { let push = match wrapper { None => node .incr_refcount_allow_zero2one(strong, self)? - .map(|node| node as _), + .map(|node| node as DLArc), Some(wrapper) => node.incr_refcount_allow_zero2one_with_wrapper(strong, wrapper, self), }; if let Some(node) = push { @@ -741,7 +741,7 @@ impl Process { } else { (0, 0, 0) }; - let node_ref = self.get_node(ptr, cookie, flags as _, true, thread)?; + let node_ref = self.get_node(ptr, cookie, flags, true, thread)?; let node = node_ref.node.clone(); self.ctx.set_manager_node(node_ref)?; self.inner.lock().is_manager = true; @@ -1522,7 +1522,7 @@ fn get_frozen_status(data: UserSlice) -> Result { for ctx in crate::context::get_all_contexts()? { ctx.for_each_proc(|proc| { - if proc.task.pid() == info.pid as _ { + if proc.task.pid() == info.pid as Pid { found = true; let inner = proc.inner.lock(); let txns_pending = inner.txns_pending_locked(); diff --git a/drivers/android/binder/rust_binder_main.rs b/drivers/android/binder/rust_binder_main.rs index 88da29413e16..2c10a8cd3d88 100644 --- a/drivers/android/binder/rust_binder_main.rs +++ b/drivers/android/binder/rust_binder_main.rs @@ -6,7 +6,7 @@ #![crate_name = "rust_binder"] #![recursion_limit = "256"] -#![allow(clippy::as_underscore, clippy::cast_lossless)] +#![allow(clippy::cast_lossless)] use kernel::{ bindings::{self, seq_file}, @@ -412,7 +412,7 @@ unsafe extern "C" fn rust_binder_ioctl( // SAFETY: We previously set `private_data` in `rust_binder_open`. let f = unsafe { Arc::::borrow((*file).private_data) }; // SAFETY: The caller ensures that the file is valid. - match Process::ioctl(f, unsafe { File::from_raw_file(file) }, cmd as _, arg as _) { + match Process::ioctl(f, unsafe { File::from_raw_file(file) }, cmd, arg) { Ok(()) => 0, Err(err) => err.to_errno() as isize, } diff --git a/drivers/android/binder/thread.rs b/drivers/android/binder/thread.rs index 97d5f31e8fe3..87298a8c597d 100644 --- a/drivers/android/binder/thread.rs +++ b/drivers/android/binder/thread.rs @@ -666,9 +666,9 @@ impl Thread { let strong = obj.hdr.type_ == BINDER_TYPE_BINDER; // SAFETY: `binder` is a `binder_uintptr_t`; any bit pattern is a valid // representation. - let ptr = unsafe { obj.__bindgen_anon_1.binder } as _; - let cookie = obj.cookie as _; - let flags = obj.flags as _; + let ptr = unsafe { obj.__bindgen_anon_1.binder }; + let cookie = obj.cookie; + let flags = obj.flags; let node = self .process .as_arc_borrow() @@ -679,7 +679,7 @@ impl Thread { BinderObjectRef::Handle(obj) => { let strong = obj.hdr.type_ == BINDER_TYPE_HANDLE; // SAFETY: `handle` is a `u32`; any bit pattern is a valid representation. - let handle = unsafe { obj.__bindgen_anon_1.handle } as _; + let handle = unsafe { obj.__bindgen_anon_1.handle }; let node = self.process.get_node_from_handle(handle, strong)?; security::binder_transfer_binder(&self.process.cred, &view.alloc.process.cred)?; view.transfer_binder_object(offset, obj, strong, node)?; @@ -736,7 +736,7 @@ impl Thread { ScatterGatherEntry { obj_index, offset: alloc_offset, - sender_uaddr: obj.buffer as _, + sender_uaddr: obj.buffer as usize, length: obj_length, pointer_fixups: KVec::new(), fixup_min_offset: 0, @@ -843,7 +843,7 @@ impl Thread { .ok_or(EINVAL)?; let mut fda_bytes = KVec::new(); - UserSlice::new(UserPtr::from_addr(fda_uaddr as _), fds_len) + UserSlice::new(UserPtr::from_addr(fda_uaddr as usize), fds_len) .read_all(&mut fda_bytes, GFP_KERNEL)?; if fds_len != fda_bytes.len() { @@ -1365,7 +1365,7 @@ impl Thread { let write_start = req.write_buffer.wrapping_add(req.write_consumed); let write_len = req.write_size.saturating_sub(req.write_consumed); let mut reader = - UserSlice::new(UserPtr::from_addr(write_start as _), write_len as _).reader(); + UserSlice::new(UserPtr::from_addr(write_start as usize), write_len as usize).reader(); while reader.len() >= size_of::() && self.inner.lock().return_work.is_unused() { let before = reader.len(); @@ -1436,7 +1436,7 @@ impl Thread { let read_start = req.read_buffer.wrapping_add(req.read_consumed); let read_len = req.read_size.saturating_sub(req.read_consumed); let mut writer = BinderReturnWriter::new( - UserSlice::new(UserPtr::from_addr(read_start as _), read_len as _).writer(), + UserSlice::new(UserPtr::from_addr(read_start as usize), read_len as usize).writer(), self, ); let (in_pool, has_transaction, thread_todo, use_proc_queue) = { @@ -1500,9 +1500,11 @@ impl Thread { // Write BR_SPAWN_LOOPER if the process needs more threads for its pool. if has_noop_placeholder && in_pool && self.process.needs_thread() { - let mut writer = - UserSlice::new(UserPtr::from_addr(req.read_buffer as _), req.read_size as _) - .writer(); + let mut writer = UserSlice::new( + UserPtr::from_addr(req.read_buffer as usize), + req.read_size as usize, + ) + .writer(); writer.write(&BR_SPAWN_LOOPER)?; } Ok(()) diff --git a/drivers/android/binder/transaction.rs b/drivers/android/binder/transaction.rs index 1d9b66920a21..38795224a784 100644 --- a/drivers/android/binder/transaction.rs +++ b/drivers/android/binder/transaction.rs @@ -11,6 +11,7 @@ use kernel::{ task::{Kuid, Pid}, time::{Instant, Monotonic}, types::ScopeGuard, + uapi, }; use crate::{ @@ -411,16 +412,17 @@ impl DeliverToRead for Transaction { let tr = tr_sec.tr_data(); if let Some(target_node) = &self.target_node { let (ptr, cookie) = target_node.get_id(); - tr.target.ptr = ptr as _; - tr.cookie = cookie as _; + tr.target.ptr = ptr as uapi::binder_uintptr_t; + tr.cookie = cookie as uapi::binder_uintptr_t; }; tr.code = self.code; tr.flags = self.flags; - tr.data_size = self.data_size as _; - tr.data.ptr.buffer = self.data_address as _; - tr.offsets_size = self.offsets_size as _; + tr.data_size = self.data_size as uapi::binder_size_t; + tr.data.ptr.buffer = self.data_address as uapi::binder_uintptr_t; + tr.offsets_size = self.offsets_size as uapi::binder_size_t; if tr.offsets_size > 0 { - tr.data.ptr.offsets = (self.data_address + ptr_align(self.data_size).unwrap()) as _; + tr.data.ptr.offsets = + (self.data_address + ptr_align(self.data_size).unwrap()) as uapi::binder_uintptr_t; } tr.sender_euid = self.sender_euid.into_uid_in_current_ns(); tr.sender_pid = 0; From 9e32d2a9784736b3fc262f51ddda1141de753314 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Tue, 26 May 2026 14:39:11 -0400 Subject: [PATCH 005/135] rust: binder: enable `clippy::cast_lossless` Before Rust 1.29.0, Clippy introduced the `cast_lossless` lint [1]: > Rust's `as` keyword will perform many kinds of conversions, including > silently lossy conversions. Conversion functions such as `i32::from` > will only perform lossless conversions. Using the conversion functions > prevents conversions from becoming silently lossy if the input types > ever change, and makes it clear for people reading the code that the > conversion is lossless. While this does not eliminate unchecked `as` conversions, it makes such conversions easier to scrutinize. It also has the slight benefit of removing a degree of freedom on which to bikeshed. Thus apply the changes and enable the lint in the Binder Rust driver -- no functional change intended. Link: https://rust-lang.github.io/rust-clippy/master/index.html#cast_lossless [1] Reviewed-by: Alice Ryhl Assisted-by: Codex:gpt-5 Signed-off-by: Tamir Duberstein Link: https://patch.msgid.link/20260526-binder-strict-provenance-v2-5-a41d89c29bc5@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/freeze.rs | 2 +- drivers/android/binder/process.rs | 6 +++--- drivers/android/binder/rust_binder_main.rs | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/android/binder/freeze.rs b/drivers/android/binder/freeze.rs index 53b60035639a..2178258772e5 100644 --- a/drivers/android/binder/freeze.rs +++ b/drivers/android/binder/freeze.rs @@ -127,7 +127,7 @@ impl DeliverToRead for FreezeMessage { } let mut state_info = BinderFrozenStateInfo::default(); - state_info.is_frozen = is_frozen as u32; + state_info.is_frozen = u32::from(is_frozen); state_info.cookie = freeze.cookie.0; freeze.is_pending = true; freeze.last_is_frozen = Some(is_frozen); diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs index 49ca1bcd21a1..99d1a7ade599 100644 --- a/drivers/android/binder/process.rs +++ b/drivers/android/binder/process.rs @@ -1526,9 +1526,9 @@ fn get_frozen_status(data: UserSlice) -> Result { found = true; let inner = proc.inner.lock(); let txns_pending = inner.txns_pending_locked(); - info.async_recv |= inner.async_recv as u32; - info.sync_recv |= inner.sync_recv as u32; - info.sync_recv |= (txns_pending as u32) << 1; + info.async_recv |= u32::from(inner.async_recv); + info.sync_recv |= u32::from(inner.sync_recv); + info.sync_recv |= u32::from(txns_pending) << 1; } }); } diff --git a/drivers/android/binder/rust_binder_main.rs b/drivers/android/binder/rust_binder_main.rs index 2c10a8cd3d88..432390aab25b 100644 --- a/drivers/android/binder/rust_binder_main.rs +++ b/drivers/android/binder/rust_binder_main.rs @@ -6,7 +6,6 @@ #![crate_name = "rust_binder"] #![recursion_limit = "256"] -#![allow(clippy::cast_lossless)] use kernel::{ bindings::{self, seq_file}, From b9d17aa74ddd79e2d081db5aacccc2992efceb4c Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Mon, 15 Jun 2026 12:36:41 +0000 Subject: [PATCH 006/135] rust_binder: avoid allocating under node_refs for freeze listeners The node_refs mutex needs to be changed to a spinlock, so in preparation for that, update freeze.rs to avoid allocating under the node_refs lock. This is done by adding a retry loop so that if add_freeze_listener() requires reallocating the KVVec<_> of freeze listeners, the caller will allocate a larger vector and retry. Analogously, the remove_freeze_listener() function is updated to return the empty KVVec<_> when it is no longer needed, to avoid calling kvfree() under the node_refs lock. Reviewed-by: Matthew Maurer Signed-off-by: Alice Ryhl Link: https://patch.msgid.link/20260615-binder-noderefs-spin-v3-1-3235f5a3e0a0@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/freeze.rs | 65 +++++++++++++++++++++----------- drivers/android/binder/node.rs | 47 +++++++++++------------ 2 files changed, 67 insertions(+), 45 deletions(-) diff --git a/drivers/android/binder/freeze.rs b/drivers/android/binder/freeze.rs index 2178258772e5..918c4e98b66f 100644 --- a/drivers/android/binder/freeze.rs +++ b/drivers/android/binder/freeze.rs @@ -173,36 +173,58 @@ impl Process { let msg = FreezeMessage::new(GFP_KERNEL)?; let alloc = RBTreeNodeReservation::new(GFP_KERNEL)?; + let mut afl_vec_alloc = KVVec::new(); + let mut info; + let mut freeze_entry; let mut node_refs_guard = self.node_refs.lock(); - let node_refs = &mut *node_refs_guard; - let Some(info) = node_refs.by_handle.get_mut(&handle) else { - pr_warn!("BC_REQUEST_FREEZE_NOTIFICATION invalid ref {}\n", handle); - return Err(EINVAL); - }; - if info.freeze().is_some() { - pr_warn!("BC_REQUEST_FREEZE_NOTIFICATION already set\n"); - return Err(EINVAL); - } - let node_ref = info.node_ref(); - let freeze_entry = node_refs.freeze_listeners.entry(cookie); - - if let rbtree::Entry::Occupied(ref dupe) = freeze_entry { - if !dupe.get().allow_duplicate(&node_ref.node) { - pr_warn!("BC_REQUEST_FREEZE_NOTIFICATION duplicate cookie\n"); + loop { + let node_refs = &mut *node_refs_guard; + info = match node_refs.by_handle.get_mut(&handle) { + Some(info) => info, + None => { + pr_warn!("BC_REQUEST_FREEZE_NOTIFICATION invalid ref {}\n", handle); + return Err(EINVAL); + } + }; + if info.freeze().is_some() { + pr_warn!("BC_REQUEST_FREEZE_NOTIFICATION already set\n"); return Err(EINVAL); } - } + let node_ref = info.node_ref(); + freeze_entry = node_refs.freeze_listeners.entry(cookie); - // All failure paths must come before this call, and all modifications must come after this - // call. - node_ref.node.add_freeze_listener(self, GFP_KERNEL)?; + if let rbtree::Entry::Occupied(ref dupe) = freeze_entry { + if !dupe.get().allow_duplicate(&node_ref.node) { + pr_warn!("BC_REQUEST_FREEZE_NOTIFICATION duplicate cookie\n"); + return Err(EINVAL); + } + } + + // Now we add to the node's freeze listener list, with retry and re-allocate if the + // vector is full. + // + // To ensure that the node is added atomically, this is the first time we modify any + // state. When this call succeeds, all other modifications must occur without the + // possibility for any failure paths. + match node_ref + .node + .add_freeze_listener(self, &mut afl_vec_alloc)? + { + Ok(()) => break, + Err(resize_target) => { + drop(node_refs_guard); + afl_vec_alloc = KVVec::with_capacity(resize_target, GFP_KERNEL)?; + node_refs_guard = self.node_refs.lock(); + } + } + } match freeze_entry { rbtree::Entry::Vacant(entry) => { entry.insert( FreezeListener { cookie, - node: node_ref.node.clone(), + node: info.node_ref().node.clone(), last_is_frozen: None, is_pending: false, is_clearing: false, @@ -273,6 +295,7 @@ impl Process { let handle = hc.handle; let cookie = FreezeCookie(hc.cookie); + let _to_free_fl; let alloc = FreezeMessage::new(GFP_KERNEL)?; let mut node_refs_guard = self.node_refs.lock(); let node_refs = &mut *node_refs_guard; @@ -293,7 +316,7 @@ impl Process { return Err(EINVAL); }; listener.is_clearing = true; - listener.node.remove_freeze_listener(self); + _to_free_fl = listener.node.remove_freeze_listener(self); *info.freeze() = None; let mut msg = None; if !listener.is_pending { diff --git a/drivers/android/binder/node.rs b/drivers/android/binder/node.rs index 4e75f58bf0db..fb57c0b20888 100644 --- a/drivers/android/binder/node.rs +++ b/drivers/android/binder/node.rs @@ -659,33 +659,29 @@ impl Node { pub(crate) fn add_freeze_listener( &self, process: &Arc, - flags: kernel::alloc::Flags, - ) -> Result { - let mut vec_alloc = KVVec::>::new(); - loop { - let mut guard = self.owner.inner.lock(); - // Do not check for `guard.dead`. The `dead` flag that matters here is the owner of the - // listener, no the target. - let inner = self.inner.access_mut(&mut guard); - let len = inner.freeze_list.len(); - if len >= inner.freeze_list.capacity() { - if len >= vec_alloc.capacity() { - drop(guard); - vec_alloc = KVVec::with_capacity((1 + len).next_power_of_two(), flags)?; - continue; - } - mem::swap(&mut inner.freeze_list, &mut vec_alloc); - for elem in vec_alloc.drain_all() { - inner.freeze_list.push_within_capacity(elem)?; - } + // If the vector needs to be resized, it's done via this argument. + vec_alloc: &mut KVVec>, + ) -> Result> { + let mut guard = self.owner.inner.lock(); + // Do not check for `guard.dead`. The `dead` flag that matters here is the owner of the + // listener, not the target. + let inner = self.inner.access_mut(&mut guard); + let len = inner.freeze_list.len(); + if len == inner.freeze_list.capacity() { + if len >= vec_alloc.capacity() { + // Request the caller to reallocate. + return Ok(Err((1 + len).next_power_of_two())); + } + mem::swap(&mut inner.freeze_list, vec_alloc); + for elem in vec_alloc.drain_all() { + inner.freeze_list.push_within_capacity(elem)?; } - inner.freeze_list.push_within_capacity(process.clone())?; - return Ok(()); } + inner.freeze_list.push_within_capacity(process.clone())?; + Ok(Ok(())) } - pub(crate) fn remove_freeze_listener(&self, p: &Arc) { - let _unused_capacity; + pub(crate) fn remove_freeze_listener(&self, p: &Arc) -> KVVec> { let mut guard = self.owner.inner.lock(); let inner = self.inner.access_mut(&mut guard); let len = inner.freeze_list.len(); @@ -696,9 +692,12 @@ impl Node { p.pid_in_current_ns() ); } + // If the vector is empty it needs to be freed. However, we can't free it here because that + // might sleep, so return it to the caller. if inner.freeze_list.is_empty() { - _unused_capacity = mem::take(&mut inner.freeze_list); + return mem::take(&mut inner.freeze_list); } + KVVec::new() } pub(crate) fn freeze_list<'a>(&'a self, guard: &'a ProcessInner) -> &'a [Arc] { From 521eae8326a18cbf7fb4640dcfb2d1396423d1ab Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Mon, 15 Jun 2026 12:36:42 +0000 Subject: [PATCH 007/135] rust_binder: avoid dropping NodeRef in update_ref() under lock In preparation for changing the node_refs lock to a spinlock, move the cleanup of NodeRefInfo in update_ref() so that it occurs without the node_refs lock held. This avoids dropping an Arc with the lock held. Furthermore, the NodeDeath field is kept in the NodeRefInfo to be dropped outside the lock as well. The removal from the rbtree is updated to use remove_node(), which keeps the rbtree node allocation until after node_refs is unlocked as well. This is not strictly necessary as it just moves a kfree() outside the lock, but there's no reason to invoke the kfree() under the lock if we can easily avoid it, so avoid it. Reviewed-by: Matthew Maurer Signed-off-by: Alice Ryhl Link: https://patch.msgid.link/20260615-binder-noderefs-spin-v3-2-3235f5a3e0a0@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/process.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs index 99d1a7ade599..73a2582c4d9c 100644 --- a/drivers/android/binder/process.rs +++ b/drivers/android/binder/process.rs @@ -942,13 +942,17 @@ impl Process { // To preserve original binder behaviour, we only fail requests where the manager tries to // increment references on itself. + let _to_free_by_handle; + let _to_free_by_node; let mut refs = self.node_refs.lock(); if let Some(info) = refs.by_handle.get_mut(&handle) { if info.node_ref().update(inc, strong) { // Clean up death if there is one attached to this node reference. - if let Some(death) = info.death().take() { + // + // We remove the entire `info` below, so no need to remove `death` from `info`. + if let Some(death) = info.death().as_ref() { death.set_cleared(true); - self.remove_from_delivered_deaths(&death); + self.remove_from_delivered_deaths(death); } // Remove reference from process tables, and from the node's `refs` list. @@ -957,8 +961,8 @@ impl Process { unsafe { info.node_ref2().node.remove_node_info(info) }; let id = info.node_ref().node.global_id(); - refs.by_handle.remove(&handle); - refs.by_node.remove(&id); + _to_free_by_handle = refs.by_handle.remove_node(&handle); + _to_free_by_node = refs.by_node.remove_node(&id); refs.handle_is_present.release_id(handle as usize); if let Some(shrink) = refs.handle_is_present.shrink_request() { From 56c650167ea9627ba734e375bff1b68d2039b88a Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Mon, 15 Jun 2026 12:36:43 +0000 Subject: [PATCH 008/135] rust_binder: schedule NodeDeath outside of node_refs lock There's no reason to hold the node_refs lock while scheduling the NodeDeath to the thread todo list, so don't. The call to set_cleared() is kept under the lock so that the state update is kept atomic. Reviewed-by: Matthew Maurer Signed-off-by: Alice Ryhl Link: https://patch.msgid.link/20260615-binder-noderefs-spin-v3-3-3235f5a3e0a0@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/process.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs index 73a2582c4d9c..7b214dc51586 100644 --- a/drivers/android/binder/process.rs +++ b/drivers/android/binder/process.rs @@ -1289,7 +1289,10 @@ impl Process { // Update state and determine if we need to queue a work item. We only need to do it when // the node is not dead or if the user already completed the death notification. - if death.set_cleared(false) { + let should_schedule = death.set_cleared(false); + drop(refs); + + if should_schedule { if let Some(death) = ListArc::try_from_arc_or_drop(death) { let _ = thread.push_work_if_looper(death); } From 2812b20e165dbd9764d31d25686e305c7f329010 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Mon, 15 Jun 2026 12:36:44 +0000 Subject: [PATCH 009/135] rust_binder: keep NodeDeath in NodeRefInfo during process cleanup By keeping the NodeDeath inside the NodeRefInfo structure during process cleanup, we avoid running its destructor under the node_refs lock. It is still dropped shortly thereafter when the entire rbtree holding the NodeRefInfo objects is dropped, but that occurs outside of the lock. Reviewed-by: Matthew Maurer Signed-off-by: Alice Ryhl Link: https://patch.msgid.link/20260615-binder-noderefs-spin-v3-4-3235f5a3e0a0@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/process.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs index 7b214dc51586..7a45d478e33a 100644 --- a/drivers/android/binder/process.rs +++ b/drivers/android/binder/process.rs @@ -1375,13 +1375,11 @@ impl Process { // SAFETY: We are removing the `NodeRefInfo` from the right node. unsafe { info.node_ref2().node.remove_node_info(info) }; - // Remove all death notifications from the nodes (that belong to a different process). - let death = if let Some(existing) = info.death().take() { - existing - } else { - continue; - }; - death.set_cleared(false); + // Clear death notifications from the nodes (that belong to a different process). + // No need to remove them from `info` as we clear info below. + if let Some(death) = info.death().as_ref() { + death.set_cleared(false); + } } // Clean up freeze listeners. From 63b4af40e260cf472c2946459a62060983451668 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Mon, 15 Jun 2026 12:36:45 +0000 Subject: [PATCH 010/135] rust_binder: avoid destructors in insert_or_update_handle() The insert_or_update_handle() function currently has two places where it drops objects under the node_refs lock. In preparation for changing node_refs into a spinlock, update the code to either entirely remove the codepath or drop the node_refs lock first before running the destructor. This also has the side-benefit that we avoid traversing the by_node rbtree twice. Currently it's first traversed to see if the new node is present, and then traversed again to insert it. By saving the VacantEntry from the first lookup, we can perform the insertion without traversing the tree again. Reviewed-by: Matthew Maurer Signed-off-by: Alice Ryhl Link: https://patch.msgid.link/20260615-binder-noderefs-spin-v3-5-3235f5a3e0a0@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/process.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs index 7a45d478e33a..7a21e4475c80 100644 --- a/drivers/android/binder/process.rs +++ b/drivers/android/binder/process.rs @@ -861,14 +861,17 @@ impl Process { let handle = unused_id.as_u32(); // Do a lookup again as node may have been inserted before the lock was reacquired. - if let Some(handle_ref) = refs.by_node.get(&node_ref.node.global_id()) { - let handle = *handle_ref; - let info = refs.by_handle.get_mut(&handle).unwrap(); - info.node_ref().absorb(node_ref); - return Ok(handle); - } + let by_node_slot = match refs.by_node.entry(node_ref.node.global_id()) { + rbtree::Entry::Vacant(by_node_slot) => by_node_slot, + rbtree::Entry::Occupied(handle_ref) => { + // The node was inserted by another thread while we didn't hold the lock. + let handle = handle_ref.get(); + let info = refs.by_handle.get_mut(handle).unwrap(); + info.node_ref().absorb(node_ref); + return Ok(*handle); + } + }; - let gid = node_ref.node.global_id(); let (info_proc, info_node) = { let info_init = NodeRefInfo::new(node_ref, handle, self.into()); match info.pin_init_with(info_init) { @@ -884,6 +887,9 @@ impl Process { // first thing in `deferred_release`, process cleanup will not miss the items inserted into // `refs` below. if self.inner.lock().is_dead { + // Explicitly drop the lock so that `info_proc` and `info_node` are dropped outside of + // the lock. + drop(refs_lock); return Err(ESRCH); } @@ -891,7 +897,7 @@ impl Process { // `info_node` into the right node's `refs` list. unsafe { info_proc.node_ref2().node.insert_node_info(info_node) }; - refs.by_node.insert(reserve1.into_node(gid, handle)); + by_node_slot.insert(handle, reserve1); by_handle_slot.insert(info_proc, reserve2); unused_id.acquire(); Ok(handle) From f8d269390cd2a7a9fb5a31f153e7c7b709defea0 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Mon, 15 Jun 2026 12:36:46 +0000 Subject: [PATCH 011/135] rust_binder: update Process::node_refs to use SpinLock Unfortunately the current use of a mutex for this lock leads to priority inversion. Traces have been observed where a process is trying to obtain this mutex for 22ms, but it's unable to do so because the thread holding the lock is scheduled out. Since this occurred on a UI thread, that is an extremely long delay. Code paths that might sleep under this lock have already been updated in patches leading up to this one. Reviewed-by: Matthew Maurer Signed-off-by: Alice Ryhl Link: https://patch.msgid.link/20260615-binder-noderefs-spin-v3-6-3235f5a3e0a0@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/process.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs index 7a21e4475c80..1abeb83684e4 100644 --- a/drivers/android/binder/process.rs +++ b/drivers/android/binder/process.rs @@ -30,7 +30,7 @@ use kernel::{ sync::{ aref::ARef, lock::{spinlock::SpinLockBackend, Guard}, - Arc, ArcBorrow, CondVar, CondVarTimeoutResult, Mutex, SpinLock, UniqueArc, + Arc, ArcBorrow, CondVar, CondVarTimeoutResult, SpinLock, UniqueArc, }, task::{Pid, Task}, uaccess::{UserSlice, UserSliceReader}, @@ -455,7 +455,7 @@ pub(crate) struct Process { // Node references are in a different lock to avoid recursive acquisition when // incrementing/decrementing a node in another process. #[pin] - node_refs: Mutex, + node_refs: SpinLock, // Work node for deferred work item. #[pin] @@ -510,7 +510,7 @@ impl Process { cred, inner <- kernel::new_spinlock!(ProcessInner::new(), "Process::inner"), pages <- ShrinkablePageRange::new(&super::BINDER_SHRINKER), - node_refs <- kernel::new_mutex!(ProcessNodeRefs::new(), "Process::node_refs"), + node_refs <- kernel::new_spinlock!(ProcessNodeRefs::new(), "Process::node_refs"), freeze_wait <- kernel::new_condvar!("Process::freeze_wait"), task: current.group_leader().into(), defer_work <- kernel::new_work!("Process::defer_work"), From 28a561eeaf656f6a18703ec1f54143dd68598b2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Thu, 28 May 2026 17:23:55 +0000 Subject: [PATCH 012/135] gpib: fmh_gpib: Fix typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix typo: Hueristically -> Heuristically Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260528172306.34050-1-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/fmh_gpib/fmh_gpib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpib/fmh_gpib/fmh_gpib.c b/drivers/gpib/fmh_gpib/fmh_gpib.c index fcafdc02ea2e..5e10e9353fed 100644 --- a/drivers/gpib/fmh_gpib/fmh_gpib.c +++ b/drivers/gpib/fmh_gpib/fmh_gpib.c @@ -1338,7 +1338,7 @@ static int fmh_gpib_init(struct fmh_priv *e_priv, struct gpib_board *board, int write_byte(nec_priv, AUX_LO_SPEED, AUXMR); nec7210_set_handshake_mode(board, nec_priv, handshake_mode); - /* Hueristically check if hardware supports fifo half full/empty interrupts */ + /* Heuristically check if hardware supports fifo half full/empty interrupts */ fifo_status_bits = fifos_read(e_priv, FIFO_CONTROL_STATUS_REG); e_priv->supports_fifo_interrupts = (fifo_status_bits & TX_FIFO_EMPTY) && (fifo_status_bits & TX_FIFO_HALF_EMPTY); From 75f9481e0479c3faadf4d88baffe84b3d23d5763 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 26 May 2026 08:13:15 +0200 Subject: [PATCH 013/135] tlclk: if sscanf() fails, fall back to 0, not random value If sscanf(IN, FMT, &OUT) fails, OUT may be unchanged. So if OUT was never initialized, it may be still uninitialized memory. To prevent such, initialize OUT=0 first. Fixes: 648bf4fb21f5 ("[PATCH] tlclk driver update") Fixes: 1a80ba882730 ("[PATCH] Telecom Clock Driver for MPCBL0010 ATCA computer blade") Signed-off-by: Alexander A. Klimov Link: https://patch.msgid.link/20260526061321.6123-4-grandmaster@al2klimov.de Signed-off-by: Greg Kroah-Hartman --- drivers/char/tlclk.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/drivers/char/tlclk.c b/drivers/char/tlclk.c index dd45fe5eb6f2..255f69123af5 100644 --- a/drivers/char/tlclk.c +++ b/drivers/char/tlclk.c @@ -328,7 +328,7 @@ static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL); static ssize_t store_received_ref_clk3a(struct device *d, struct device_attribute *attr, const char *buf, size_t count) { - unsigned long tmp; + unsigned long tmp = 0; unsigned char val; unsigned long flags; @@ -350,7 +350,7 @@ static DEVICE_ATTR(received_ref_clk3a, (S_IWUSR|S_IWGRP), NULL, static ssize_t store_received_ref_clk3b(struct device *d, struct device_attribute *attr, const char *buf, size_t count) { - unsigned long tmp; + unsigned long tmp = 0; unsigned char val; unsigned long flags; @@ -372,7 +372,7 @@ static DEVICE_ATTR(received_ref_clk3b, (S_IWUSR|S_IWGRP), NULL, static ssize_t store_enable_clk3b_output(struct device *d, struct device_attribute *attr, const char *buf, size_t count) { - unsigned long tmp; + unsigned long tmp = 0; unsigned char val; unsigned long flags; @@ -394,7 +394,7 @@ static ssize_t store_enable_clk3a_output(struct device *d, struct device_attribute *attr, const char *buf, size_t count) { unsigned long flags; - unsigned long tmp; + unsigned long tmp = 0; unsigned char val; sscanf(buf, "%lX", &tmp); @@ -415,7 +415,7 @@ static ssize_t store_enable_clkb1_output(struct device *d, struct device_attribute *attr, const char *buf, size_t count) { unsigned long flags; - unsigned long tmp; + unsigned long tmp = 0; unsigned char val; sscanf(buf, "%lX", &tmp); @@ -437,7 +437,7 @@ static ssize_t store_enable_clka1_output(struct device *d, struct device_attribute *attr, const char *buf, size_t count) { unsigned long flags; - unsigned long tmp; + unsigned long tmp = 0; unsigned char val; sscanf(buf, "%lX", &tmp); @@ -458,7 +458,7 @@ static ssize_t store_enable_clkb0_output(struct device *d, struct device_attribute *attr, const char *buf, size_t count) { unsigned long flags; - unsigned long tmp; + unsigned long tmp = 0; unsigned char val; sscanf(buf, "%lX", &tmp); @@ -479,7 +479,7 @@ static ssize_t store_enable_clka0_output(struct device *d, struct device_attribute *attr, const char *buf, size_t count) { unsigned long flags; - unsigned long tmp; + unsigned long tmp = 0; unsigned char val; sscanf(buf, "%lX", &tmp); @@ -500,7 +500,7 @@ static ssize_t store_select_amcb2_transmit_clock(struct device *d, struct device_attribute *attr, const char *buf, size_t count) { unsigned long flags; - unsigned long tmp; + unsigned long tmp = 0; unsigned char val; sscanf(buf, "%lX", &tmp); @@ -541,7 +541,7 @@ static DEVICE_ATTR(select_amcb2_transmit_clock, (S_IWUSR|S_IWGRP), NULL, static ssize_t store_select_amcb1_transmit_clock(struct device *d, struct device_attribute *attr, const char *buf, size_t count) { - unsigned long tmp; + unsigned long tmp = 0; unsigned char val; unsigned long flags; @@ -583,7 +583,7 @@ static DEVICE_ATTR(select_amcb1_transmit_clock, (S_IWUSR|S_IWGRP), NULL, static ssize_t store_select_redundant_clock(struct device *d, struct device_attribute *attr, const char *buf, size_t count) { - unsigned long tmp; + unsigned long tmp = 0; unsigned char val; unsigned long flags; @@ -604,7 +604,7 @@ static DEVICE_ATTR(select_redundant_clock, (S_IWUSR|S_IWGRP), NULL, static ssize_t store_select_ref_frequency(struct device *d, struct device_attribute *attr, const char *buf, size_t count) { - unsigned long tmp; + unsigned long tmp = 0; unsigned char val; unsigned long flags; @@ -625,7 +625,7 @@ static DEVICE_ATTR(select_ref_frequency, (S_IWUSR|S_IWGRP), NULL, static ssize_t store_filter_select(struct device *d, struct device_attribute *attr, const char *buf, size_t count) { - unsigned long tmp; + unsigned long tmp = 0; unsigned char val; unsigned long flags; @@ -645,7 +645,7 @@ static DEVICE_ATTR(filter_select, (S_IWUSR|S_IWGRP), NULL, store_filter_select); static ssize_t store_hardware_switching_mode(struct device *d, struct device_attribute *attr, const char *buf, size_t count) { - unsigned long tmp; + unsigned long tmp = 0; unsigned char val; unsigned long flags; @@ -666,7 +666,7 @@ static DEVICE_ATTR(hardware_switching_mode, (S_IWUSR|S_IWGRP), NULL, static ssize_t store_hardware_switching(struct device *d, struct device_attribute *attr, const char *buf, size_t count) { - unsigned long tmp; + unsigned long tmp = 0; unsigned char val; unsigned long flags; @@ -687,7 +687,7 @@ static DEVICE_ATTR(hardware_switching, (S_IWUSR|S_IWGRP), NULL, static ssize_t store_refalign (struct device *d, struct device_attribute *attr, const char *buf, size_t count) { - unsigned long tmp; + unsigned long tmp = 0; unsigned long flags; sscanf(buf, "%lX", &tmp); @@ -706,7 +706,7 @@ static DEVICE_ATTR(refalign, (S_IWUSR|S_IWGRP), NULL, store_refalign); static ssize_t store_mode_select (struct device *d, struct device_attribute *attr, const char *buf, size_t count) { - unsigned long tmp; + unsigned long tmp = 0; unsigned char val; unsigned long flags; @@ -726,7 +726,7 @@ static DEVICE_ATTR(mode_select, (S_IWUSR|S_IWGRP), NULL, store_mode_select); static ssize_t store_reset (struct device *d, struct device_attribute *attr, const char *buf, size_t count) { - unsigned long tmp; + unsigned long tmp = 0; unsigned char val; unsigned long flags; From 7ddb521ab097413fbdff483b53b4a8c73a0e2b40 Mon Sep 17 00:00:00 2001 From: Gui-Dong Han Date: Fri, 22 May 2026 15:34:47 +0800 Subject: [PATCH 014/135] gpib: Move stuck SRQ update under lock Move the stuck SRQ state update into autopoll_all_devices() and keep it under big_gpib_mutex. Except for initialization, keep the stuck_srq users under this mutex. autopoll_all_devices() is only called by autospoll_thread(), so there is no need to return to autospoll_thread() and set this state after dropping big_gpib_mutex. Without the mutex, a newly opened device can clear stuck_srq and have that clear overwritten by the previous autospoll result: autospoll: serial_poll_all() returns 0 and unlocks big_gpib_mutex open_dev_ioctl: open new device and clear stuck_srq with big_gpib_mutex held autospoll: set stuck_srq That leaves the board marked stuck again after the new device is opened. autospoll_wait_should_wake_up() then refuses to poll while stuck_srq is set, so later SRQ handling can be mistakenly suppressed. Without the mutex, atomic_set() and set_bit() only make individual updates atomic. They do not order the two updates or make stuck_srq and status visible as a consistent pair. Taking big_gpib_mutex serializes the state transition with the other runtime users. Keep the existing wakeup behavior unchanged and only move the stuck SRQ state update under the mutex. Fixes: 9dde4559e939 ("staging: gpib: Add GPIB common core driver") Signed-off-by: Gui-Dong Han Link: https://patch.msgid.link/20260522073447.4117690-1-hanguidong02@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/common/gpib_os.c | 21 +++++++++++---------- drivers/gpib/common/iblib.c | 3 --- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/drivers/gpib/common/gpib_os.c b/drivers/gpib/common/gpib_os.c index 69f6aa73ab9a..384800e6bf97 100644 --- a/drivers/gpib/common/gpib_os.c +++ b/drivers/gpib/common/gpib_os.c @@ -289,18 +289,19 @@ int autopoll_all_devices(struct gpib_board *board) dev_dbg(board->gpib_dev, "autopoll has board lock\n"); retval = serial_poll_all(board, serial_timeout); - if (retval < 0) { - mutex_unlock(&board->big_gpib_mutex); - mutex_unlock(&board->user_mutex); - return retval; + if (retval >= 0) { + dev_dbg(board->gpib_dev, "complete\n"); + /* + * need to wake wait queue in case someone is + * waiting on RQS + */ + wake_up_interruptible(&board->wait); } - dev_dbg(board->gpib_dev, "complete\n"); - /* - * need to wake wait queue in case someone is - * waiting on RQS - */ - wake_up_interruptible(&board->wait); + if (retval <= 0) { + atomic_set(&board->stuck_srq, 1); + set_bit(SRQI_NUM, &board->status); + } mutex_unlock(&board->big_gpib_mutex); mutex_unlock(&board->user_mutex); diff --git a/drivers/gpib/common/iblib.c b/drivers/gpib/common/iblib.c index b672dd6aad25..511e1d61c1fb 100644 --- a/drivers/gpib/common/iblib.c +++ b/drivers/gpib/common/iblib.c @@ -193,9 +193,6 @@ static int autospoll_thread(void *board_void) } if (retval <= 0) { dev_err(board->gpib_dev, "stuck SRQ\n"); - - atomic_set(&board->stuck_srq, 1); // XXX could be better - set_bit(SRQI_NUM, &board->status); } } return retval; From 6cc667892818fe44bef02193dfb9c3f843ade2b2 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 11 Jun 2026 15:10:07 +0200 Subject: [PATCH 015/135] gpib: use 'static inline' instead of 'extern inline' With GNU inline semantics, an 'extern inline' function is only included in the build if it can be inlined. When the compiler for some reason decides against inlining it, this causes a link failure, as observed in one function in the tnt4882_gpib driver: ld.lld: error: undefined symbol: mite_irq >>> referenced by tnt4882_gpib.c:974 (/home/arnd/arm-soc/drivers/gpib/tnt4882/tnt4882_gpib.c:974) >>> drivers/gpib/tnt4882/tnt4882_gpib.o:(ni_pci_attach) in archive vmlinux.a Change all of the 'extern inline' definitions in gpib to the regular 'static inline' to avoid this. Fixes: 0cd5b05551e0 ("staging: gpib: Add TNT4882 chip based GPIB driver") Fixes: 6c52d5e3cde2 ("staging: gpib: Add common include files for GPIB drivers") Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20260611131018.3662609-1-arnd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/include/amccs5933.h | 10 +++++----- drivers/gpib/tnt4882/mite.h | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/gpib/include/amccs5933.h b/drivers/gpib/include/amccs5933.h index d7f63c795096..f8a80bdc70dc 100644 --- a/drivers/gpib/include/amccs5933.h +++ b/drivers/gpib/include/amccs5933.h @@ -13,7 +13,7 @@ enum { }; // incoming mailbox 0-3 register offsets -extern inline int INCOMING_MAILBOX_REG(unsigned int mailbox) +static inline int INCOMING_MAILBOX_REG(unsigned int mailbox) { return (0x10 + 4 * mailbox); }; @@ -29,25 +29,25 @@ enum { }; // select byte 0 to 3 of incoming mailbox -extern inline int INBOX_BYTE_BITS(unsigned int byte) +static inline int INBOX_BYTE_BITS(unsigned int byte) { return (byte & 0x3) << 8; }; // select incoming mailbox 0 to 3 -extern inline int INBOX_SELECT_BITS(unsigned int mailbox) +static inline int INBOX_SELECT_BITS(unsigned int mailbox) { return (mailbox & 0x3) << 10; }; // select byte 0 to 3 of outgoing mailbox -extern inline int OUTBOX_BYTE_BITS(unsigned int byte) +static inline int OUTBOX_BYTE_BITS(unsigned int byte) { return (byte & 0x3); }; // select outgoing mailbox 0 to 3 -extern inline int OUTBOX_SELECT_BITS(unsigned int mailbox) +static inline int OUTBOX_SELECT_BITS(unsigned int mailbox) { return (mailbox & 0x3) << 2; }; diff --git a/drivers/gpib/tnt4882/mite.h b/drivers/gpib/tnt4882/mite.h index a1fdba9672a0..dd251afa90e3 100644 --- a/drivers/gpib/tnt4882/mite.h +++ b/drivers/gpib/tnt4882/mite.h @@ -45,12 +45,12 @@ struct mite_struct { extern struct mite_struct *mite_devices; -extern inline unsigned int mite_irq(struct mite_struct *mite) +static inline unsigned int mite_irq(struct mite_struct *mite) { return mite->pcidev->irq; }; -extern inline unsigned int mite_device_id(struct mite_struct *mite) +static inline unsigned int mite_device_id(struct mite_struct *mite) { return mite->pcidev->device; }; From a168e2cfb741d81be3736dba6dbe5edc2295aacb Mon Sep 17 00:00:00 2001 From: Zenghui Yu Date: Sun, 21 Jun 2026 15:49:26 +0800 Subject: [PATCH 016/135] gpib: lpvo_usb: fix path of the "debug" module parameter in comment The correct path of the "debug" module parameter should be /sys/module/lpvo_usb_gpib/parameters/debug. Fix it. Signed-off-by: Zenghui Yu Acked-by: Randy Dunlap Link: https://patch.msgid.link/20260621074926.11252-1-zenghui.yu@linux.dev Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/lpvo_usb_gpib/lpvo_usb_gpib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpib/lpvo_usb_gpib/lpvo_usb_gpib.c b/drivers/gpib/lpvo_usb_gpib/lpvo_usb_gpib.c index e6ea9422d6f2..13a067010db7 100644 --- a/drivers/gpib/lpvo_usb_gpib/lpvo_usb_gpib.c +++ b/drivers/gpib/lpvo_usb_gpib/lpvo_usb_gpib.c @@ -68,7 +68,7 @@ MODULE_DEVICE_TABLE(usb, lpvo_table); * (about twice the log volume of [1]) * To switch debug level: * At module loading: modprobe lpvo_usb_gpib debug={0,1,2} - * On the fly: echo {0,1,2} > /sys/modules/lpvo_usb_gpib/parameters/debug + * On the fly: echo {0,1,2} > /sys/module/lpvo_usb_gpib/parameters/debug */ static int debug; From 9d4ce1c7cd35f6333785d0ef1c3032f6ecb1a5b8 Mon Sep 17 00:00:00 2001 From: Mohammad Shahid Date: Sun, 5 Jul 2026 10:47:01 +0530 Subject: [PATCH 017/135] gpib: lpvo_usb_gpib: use memdup_user() instead of kmalloc() and copy_from_user() Use memdup_user() to replace the open-coded kmalloc() and copy_from_user() sequence. This simplifies the code while preserving the existing behavior. This issue was reported by memdup_user.cocci. Signed-off-by: Mohammad Shahid Link: https://patch.msgid.link/20260705051701.142070-1-mdshahid03@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/lpvo_usb_gpib/lpvo_usb_gpib.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/gpib/lpvo_usb_gpib/lpvo_usb_gpib.c b/drivers/gpib/lpvo_usb_gpib/lpvo_usb_gpib.c index 13a067010db7..dfb8f3ebc27f 100644 --- a/drivers/gpib/lpvo_usb_gpib/lpvo_usb_gpib.c +++ b/drivers/gpib/lpvo_usb_gpib/lpvo_usb_gpib.c @@ -1811,14 +1811,9 @@ static ssize_t lpvo_write(struct file *file, const char __user *user_buffer, dev = file->private_data; - buf = kmalloc(count, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - if (copy_from_user(buf, user_buffer, count)) { - kfree(buf); - return -EFAULT; - } + buf = memdup_user(user_buffer, count); + if (IS_ERR(buf)) + return PTR_ERR(buf); rv = lpvo_do_write(dev, buf, count); kfree(buf); From fbf64f3595a68b56df324bdea96fb03542ae9334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Mon, 29 Jun 2026 18:54:26 +0200 Subject: [PATCH 018/135] gpib: Initialize pci_device_ids using PCI_DEVICE macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PCI_DEVICE macro and its variants allow to initialize the first four members of a struct pci_device_id in a bit more compact form and also with an easier to grasp semantic. Explicit zeros are not needed, the compiler fills these in automatically. So convert all array members to such a macro and drop unneeded zeros. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/20260629165426.3330888-2-u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/agilent_82350b/agilent_82350b.c | 9 ++++----- drivers/gpib/cb7210/cb7210.c | 8 ++++---- drivers/gpib/cec/cec_gpib.c | 4 ++-- drivers/gpib/ines/ines_gpib.c | 21 ++++++++++++-------- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/drivers/gpib/agilent_82350b/agilent_82350b.c b/drivers/gpib/agilent_82350b/agilent_82350b.c index 9787c09faad8..490d44ad1b50 100644 --- a/drivers/gpib/agilent_82350b/agilent_82350b.c +++ b/drivers/gpib/agilent_82350b/agilent_82350b.c @@ -839,11 +839,10 @@ static int agilent_82350b_pci_probe(struct pci_dev *dev, const struct pci_device } static const struct pci_device_id agilent_82350b_pci_table[] = { - { PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050, PCI_VENDOR_ID_HP, - PCI_SUBDEVICE_ID_82350A, 0, 0, 0 }, - { PCI_VENDOR_ID_AGILENT, PCI_DEVICE_ID_82350B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, - { PCI_VENDOR_ID_AGILENT, PCI_DEVICE_ID_82351A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, - { 0 } + { PCI_VDEVICE_SUB(PLX, PCI_DEVICE_ID_PLX_9050, PCI_VENDOR_ID_HP, PCI_SUBDEVICE_ID_82350A) }, + { PCI_VDEVICE(AGILENT, PCI_DEVICE_ID_82350B) }, + { PCI_VDEVICE(AGILENT, PCI_DEVICE_ID_82351A) }, + { } }; MODULE_DEVICE_TABLE(pci, agilent_82350b_pci_table); diff --git a/drivers/gpib/cb7210/cb7210.c b/drivers/gpib/cb7210/cb7210.c index 6dd8637c5964..c62c30fc3472 100644 --- a/drivers/gpib/cb7210/cb7210.c +++ b/drivers/gpib/cb7210/cb7210.c @@ -1093,10 +1093,10 @@ static int cb7210_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) } static const struct pci_device_id cb7210_pci_table[] = { - {PCI_VENDOR_ID_CBOARDS, PCI_DEVICE_ID_CBOARDS_PCI_GPIB, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, - {PCI_VENDOR_ID_CBOARDS, PCI_DEVICE_ID_CBOARDS_CPCI_GPIB, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, - {PCI_VENDOR_ID_QUANCOM, PCI_DEVICE_ID_QUANCOM_GPIB, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, - { 0 } + { PCI_VDEVICE(CBOARDS, PCI_DEVICE_ID_CBOARDS_PCI_GPIB) }, + { PCI_VDEVICE(CBOARDS, PCI_DEVICE_ID_CBOARDS_CPCI_GPIB) }, + { PCI_VDEVICE(QUANCOM, PCI_DEVICE_ID_QUANCOM_GPIB) }, + { } }; MODULE_DEVICE_TABLE(pci, cb7210_pci_table); diff --git a/drivers/gpib/cec/cec_gpib.c b/drivers/gpib/cec/cec_gpib.c index c13bc302d9e9..530a777b1384 100644 --- a/drivers/gpib/cec/cec_gpib.c +++ b/drivers/gpib/cec/cec_gpib.c @@ -353,8 +353,8 @@ static int cec_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) } static const struct pci_device_id cec_pci_table[] = { - {CEC_VENDOR_ID, CEC_DEV_ID, PCI_ANY_ID, CEC_SUBID, 0, 0, 0 }, - {0} + { PCI_DEVICE_SUB(CEC_VENDOR_ID, CEC_DEV_ID, PCI_ANY_ID, CEC_SUBID) }, + { } }; MODULE_DEVICE_TABLE(pci, cec_pci_table); diff --git a/drivers/gpib/ines/ines_gpib.c b/drivers/gpib/ines/ines_gpib.c index 3562f3184c28..6cd6ff596fda 100644 --- a/drivers/gpib/ines/ines_gpib.c +++ b/drivers/gpib/ines/ines_gpib.c @@ -371,14 +371,19 @@ enum ines_pci_subdevice_ids { }; static struct pci_device_id ines_pci_table[] = { - {PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050, PCI_VENDOR_ID_PLX, - PCI_SUBDEVICE_ID_INES_GPIB, 0, 0, 0}, - {PCI_VENDOR_ID_AMCC, PCI_DEVICE_ID_INES_GPIB_AMCC, PCI_VENDOR_ID_AMCC, - PCI_SUBDEVICE_ID_INES_GPIB, 0, 0, 0}, - {PCI_VENDOR_ID_INES_QUICKLOGIC, PCI_DEVICE_ID_INES_GPIB_QL5030, - PCI_VENDOR_ID_INES_QUICKLOGIC, PCI_DEVICE_ID_INES_GPIB_QL5030, 0, 0, 0}, - {PCI_DEVICE(PCI_VENDOR_ID_QUANCOM, PCI_DEVICE_ID_QUANCOM_GPIB)}, - {0} + { + PCI_VDEVICE_SUB(PLX, PCI_DEVICE_ID_PLX_9050, + PCI_VENDOR_ID_PLX, PCI_SUBDEVICE_ID_INES_GPIB), + }, { + PCI_VDEVICE_SUB(AMCC, PCI_DEVICE_ID_INES_GPIB_AMCC, + PCI_VENDOR_ID_AMCC, PCI_SUBDEVICE_ID_INES_GPIB), + }, { + PCI_VDEVICE_SUB(INES_QUICKLOGIC, PCI_DEVICE_ID_INES_GPIB_QL5030, + PCI_VENDOR_ID_INES_QUICKLOGIC, PCI_DEVICE_ID_INES_GPIB_QL5030), + }, { + PCI_VDEVICE(QUANCOM, PCI_DEVICE_ID_QUANCOM_GPIB), + }, + { } }; MODULE_DEVICE_TABLE(pci, ines_pci_table); From bb14d970bf664a719570370b669a412581a77a49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Wed, 10 Jun 2026 10:31:04 +0200 Subject: [PATCH 019/135] gpib: Improve style of pnp_device_id array terminators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To match how device-id array terminators look like for other device types drop `.id = ""` from it and let the compiler care for zeroing the entry. While touching these arrays, also align spacing to how these arrays are usually written. There are no changes in the compiled drivers, only the source looks nicer. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/f23c9d77836f3b14b1efc58e3969333e8addb1c1.1781078782.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/hp_82341/hp_82341.c | 4 ++-- drivers/gpib/tnt4882/tnt4882_gpib.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpib/hp_82341/hp_82341.c b/drivers/gpib/hp_82341/hp_82341.c index 46175ba2ac36..e98cf5aea99f 100644 --- a/drivers/gpib/hp_82341/hp_82341.c +++ b/drivers/gpib/hp_82341/hp_82341.c @@ -803,8 +803,8 @@ static void hp_82341_detach(struct gpib_board *board) #if 0 /* unused, will be needed when the driver is turned into a pnp_driver */ static const struct pnp_device_id hp_82341_pnp_table[] = { - {.id = "HWP1411"}, - {.id = ""} + { .id = "HWP1411" }, + { } }; MODULE_DEVICE_TABLE(pnp, hp_82341_pnp_table); #endif diff --git a/drivers/gpib/tnt4882/tnt4882_gpib.c b/drivers/gpib/tnt4882/tnt4882_gpib.c index 51a920e1d9a4..3cd13f637ed4 100644 --- a/drivers/gpib/tnt4882/tnt4882_gpib.c +++ b/drivers/gpib/tnt4882/tnt4882_gpib.c @@ -1373,8 +1373,8 @@ static struct pci_driver tnt4882_pci_driver = { #if 0 /* unused, will be needed when the driver is turned into a pnp_driver */ static const struct pnp_device_id tnt4882_pnp_table[] = { - {.id = "NICC601"}, - {.id = ""} + { .id = "NICC601" }, + { } }; MODULE_DEVICE_TABLE(pnp, tnt4882_pnp_table); #endif From 7bf924f96d6ef4dc7700e7adf984e3b90492c435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Wed, 29 Apr 2026 18:14:53 +0200 Subject: [PATCH 020/135] parport: Consistently define pci_device_ids using named initializers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ... and PCI device helpers. The various struct pci_device_id arrays were initialized mostly by list expressions. This isn't easily readable if you're not into PCI. Use PCI_DEVICE* helper macros and named initializers which is more explicit and thus easier to parse. Also skip explicit assignments of 0 (which the compiler then takes care of). The secret plan is to make struct pci_device_id::driver_data an anonymous union (similar to https://lore.kernel.org/all/cover.1776579304.git.u.kleine-koenig@baylibre.com/) and that requires named initializers. But it's also a nice cleanup on its own. This change doesn't introduce changes to the compiled pci_device_id arrays. Tested on x86 and arm64. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/20260429161453.4154681-2-u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/parport/parport_pc.c | 204 +++++++++++----------- drivers/parport/parport_serial.c | 280 ++++++++++++++++--------------- 2 files changed, 257 insertions(+), 227 deletions(-) diff --git a/drivers/parport/parport_pc.c b/drivers/parport/parport_pc.c index c75abdd8ef25..bccfe75fe6e5 100644 --- a/drivers/parport/parport_pc.c +++ b/drivers/parport/parport_pc.c @@ -2745,116 +2745,128 @@ static struct parport_pc_pci { static const struct pci_device_id parport_pc_pci_tbl[] = { /* Super-IO onboard chips */ - { 0x1106, 0x0686, PCI_ANY_ID, PCI_ANY_ID, 0, 0, sio_via_686a }, - { 0x1106, 0x8231, PCI_ANY_ID, PCI_ANY_ID, 0, 0, sio_via_8231 }, - { PCI_VENDOR_ID_ITE, PCI_DEVICE_ID_ITE_8872, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, sio_ite_8872 }, + { PCI_DEVICE(0x1106, 0x0686), + .driver_data = sio_via_686a }, + { PCI_DEVICE(0x1106, 0x8231), + .driver_data = sio_via_8231 }, + { PCI_VDEVICE(ITE, PCI_DEVICE_ID_ITE_8872), + .driver_data = sio_ite_8872 }, /* PCI cards */ - { PCI_VENDOR_ID_SIIG, PCI_DEVICE_ID_SIIG_1P_10x, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, siig_1p_10x }, - { PCI_VENDOR_ID_SIIG, PCI_DEVICE_ID_SIIG_2P_10x, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, siig_2p_10x }, - { PCI_VENDOR_ID_SIIG, PCI_DEVICE_ID_SIIG_1P_20x, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, siig_1p_20x }, - { PCI_VENDOR_ID_SIIG, PCI_DEVICE_ID_SIIG_2P_20x, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, siig_2p_20x }, - { PCI_VENDOR_ID_LAVA, PCI_DEVICE_ID_LAVA_PARALLEL, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, lava_parallel }, - { PCI_VENDOR_ID_LAVA, PCI_DEVICE_ID_LAVA_DUAL_PAR_A, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, lava_parallel_dual_a }, - { PCI_VENDOR_ID_LAVA, PCI_DEVICE_ID_LAVA_DUAL_PAR_B, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, lava_parallel_dual_b }, - { PCI_VENDOR_ID_LAVA, PCI_DEVICE_ID_LAVA_BOCA_IOPPAR, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, boca_ioppar }, - { PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050, - PCI_SUBVENDOR_ID_EXSYS, PCI_SUBDEVICE_ID_EXSYS_4014, 0, 0, plx_9050 }, + { PCI_VDEVICE(SIIG, PCI_DEVICE_ID_SIIG_1P_10x), + .driver_data = siig_1p_10x }, + { PCI_VDEVICE(SIIG, PCI_DEVICE_ID_SIIG_2P_10x), + .driver_data = siig_2p_10x }, + { PCI_VDEVICE(SIIG, PCI_DEVICE_ID_SIIG_1P_20x), + .driver_data = siig_1p_20x }, + { PCI_VDEVICE(SIIG, PCI_DEVICE_ID_SIIG_2P_20x), + .driver_data = siig_2p_20x }, + { PCI_VDEVICE(LAVA, PCI_DEVICE_ID_LAVA_PARALLEL), + .driver_data = lava_parallel }, + { PCI_VDEVICE(LAVA, PCI_DEVICE_ID_LAVA_DUAL_PAR_A), + .driver_data = lava_parallel_dual_a }, + { PCI_VDEVICE(LAVA, PCI_DEVICE_ID_LAVA_DUAL_PAR_B), + .driver_data = lava_parallel_dual_b }, + { PCI_VDEVICE(LAVA, PCI_DEVICE_ID_LAVA_BOCA_IOPPAR), + .driver_data = boca_ioppar }, + { PCI_VDEVICE_SUB(PLX, PCI_DEVICE_ID_PLX_9050, + PCI_SUBVENDOR_ID_EXSYS, PCI_SUBDEVICE_ID_EXSYS_4014), + .driver_data = plx_9050 }, /* PCI_VENDOR_ID_TIMEDIA/SUNIX has many differing cards ...*/ - { 0x1409, 0x7268, 0x1409, 0x0101, 0, 0, timedia_4006a }, - { 0x1409, 0x7268, 0x1409, 0x0102, 0, 0, timedia_4014 }, - { 0x1409, 0x7268, 0x1409, 0x0103, 0, 0, timedia_4008a }, - { 0x1409, 0x7268, 0x1409, 0x0104, 0, 0, timedia_4018 }, - { 0x1409, 0x7268, 0x1409, 0x9018, 0, 0, timedia_9018a }, - { PCI_VENDOR_ID_SYBA, PCI_DEVICE_ID_SYBA_2P_EPP, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, syba_2p_epp }, - { PCI_VENDOR_ID_SYBA, PCI_DEVICE_ID_SYBA_1P_ECP, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, syba_1p_ecp }, - { PCI_VENDOR_ID_TITAN, PCI_DEVICE_ID_TITAN_010L, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, titan_010l }, + { PCI_DEVICE_SUB(0x1409, 0x7268, 0x1409, 0x0101), + .driver_data = timedia_4006a }, + { PCI_DEVICE_SUB(0x1409, 0x7268, 0x1409, 0x0102), + .driver_data = timedia_4014 }, + { PCI_DEVICE_SUB(0x1409, 0x7268, 0x1409, 0x0103), + .driver_data = timedia_4008a }, + { PCI_DEVICE_SUB(0x1409, 0x7268, 0x1409, 0x0104), + .driver_data = timedia_4018 }, + { PCI_DEVICE_SUB(0x1409, 0x7268, 0x1409, 0x9018), + .driver_data = timedia_9018a }, + { PCI_VDEVICE(SYBA, PCI_DEVICE_ID_SYBA_2P_EPP), + .driver_data = syba_2p_epp }, + { PCI_VDEVICE(SYBA, PCI_DEVICE_ID_SYBA_1P_ECP), + .driver_data = syba_1p_ecp }, + { PCI_VDEVICE(TITAN, PCI_DEVICE_ID_TITAN_010L), + .driver_data = titan_010l }, /* PCI_VENDOR_ID_AVLAB/Intek21 has another bunch of cards ...*/ /* AFAVLAB_TK9902 */ - { 0x14db, 0x2120, PCI_ANY_ID, PCI_ANY_ID, 0, 0, avlab_1p}, - { 0x14db, 0x2121, PCI_ANY_ID, PCI_ANY_ID, 0, 0, avlab_2p}, - { PCI_VENDOR_ID_OXSEMI, PCI_DEVICE_ID_OXSEMI_16PCI952PP, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, oxsemi_952 }, - { PCI_VENDOR_ID_OXSEMI, PCI_DEVICE_ID_OXSEMI_16PCI954PP, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, oxsemi_954 }, - { PCI_VENDOR_ID_OXSEMI, PCI_DEVICE_ID_OXSEMI_12PCI840, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, oxsemi_840 }, - { PCI_VENDOR_ID_OXSEMI, PCI_DEVICE_ID_OXSEMI_PCIe840, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, oxsemi_pcie_pport }, - { PCI_VENDOR_ID_OXSEMI, PCI_DEVICE_ID_OXSEMI_PCIe840_G, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, oxsemi_pcie_pport }, - { PCI_VENDOR_ID_OXSEMI, PCI_DEVICE_ID_OXSEMI_PCIe952_0, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, oxsemi_pcie_pport }, - { PCI_VENDOR_ID_OXSEMI, PCI_DEVICE_ID_OXSEMI_PCIe952_0_G, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, oxsemi_pcie_pport }, - { PCI_VENDOR_ID_OXSEMI, PCI_DEVICE_ID_OXSEMI_PCIe952_1, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, oxsemi_pcie_pport }, - { PCI_VENDOR_ID_OXSEMI, PCI_DEVICE_ID_OXSEMI_PCIe952_1_G, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, oxsemi_pcie_pport }, - { PCI_VENDOR_ID_OXSEMI, PCI_DEVICE_ID_OXSEMI_PCIe952_1_U, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, oxsemi_pcie_pport }, - { PCI_VENDOR_ID_OXSEMI, PCI_DEVICE_ID_OXSEMI_PCIe952_1_GU, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, oxsemi_pcie_pport }, - { PCI_VENDOR_ID_AKS, PCI_DEVICE_ID_AKS_ALADDINCARD, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, aks_0100 }, - { 0x14f2, 0x0121, PCI_ANY_ID, PCI_ANY_ID, 0, 0, mobility_pp }, + { PCI_DEVICE(0x14db, 0x2120), + .driver_data = avlab_1p }, + { PCI_DEVICE(0x14db, 0x2121), + .driver_data = avlab_2p }, + { PCI_VDEVICE(OXSEMI, PCI_DEVICE_ID_OXSEMI_16PCI952PP), + .driver_data = oxsemi_952 }, + { PCI_VDEVICE(OXSEMI, PCI_DEVICE_ID_OXSEMI_16PCI954PP), + .driver_data = oxsemi_954 }, + { PCI_VDEVICE(OXSEMI, PCI_DEVICE_ID_OXSEMI_12PCI840), + .driver_data = oxsemi_840 }, + { PCI_VDEVICE(OXSEMI, PCI_DEVICE_ID_OXSEMI_PCIe840), + .driver_data = oxsemi_pcie_pport }, + { PCI_VDEVICE(OXSEMI, PCI_DEVICE_ID_OXSEMI_PCIe840_G), + .driver_data = oxsemi_pcie_pport }, + { PCI_VDEVICE(OXSEMI, PCI_DEVICE_ID_OXSEMI_PCIe952_0), + .driver_data = oxsemi_pcie_pport }, + { PCI_VDEVICE(OXSEMI, PCI_DEVICE_ID_OXSEMI_PCIe952_0_G), + .driver_data = oxsemi_pcie_pport }, + { PCI_VDEVICE(OXSEMI, PCI_DEVICE_ID_OXSEMI_PCIe952_1), + .driver_data = oxsemi_pcie_pport }, + { PCI_VDEVICE(OXSEMI, PCI_DEVICE_ID_OXSEMI_PCIe952_1_G), + .driver_data = oxsemi_pcie_pport }, + { PCI_VDEVICE(OXSEMI, PCI_DEVICE_ID_OXSEMI_PCIe952_1_U), + .driver_data = oxsemi_pcie_pport }, + { PCI_VDEVICE(OXSEMI, PCI_DEVICE_ID_OXSEMI_PCIe952_1_GU), + .driver_data = oxsemi_pcie_pport }, + { PCI_VDEVICE(AKS, PCI_DEVICE_ID_AKS_ALADDINCARD), + .driver_data = aks_0100 }, + { PCI_DEVICE(0x14f2, 0x0121), + .driver_data = mobility_pp }, /* NetMos communication controllers */ - { PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9900, - 0xA000, 0x2000, 0, 0, netmos_9900 }, - { PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9705, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, netmos_9705 }, - { PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9715, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, netmos_9715 }, - { PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9755, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, netmos_9755 }, - { PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9805, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, netmos_9805 }, - { PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9815, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, netmos_9815 }, - { PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9901, - 0xA000, 0x2000, 0, 0, netmos_9901 }, - { PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9865, - 0xA000, 0x1000, 0, 0, netmos_9865 }, - { PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9865, - 0xA000, 0x2000, 0, 0, netmos_9865 }, + { PCI_VDEVICE_SUB(NETMOS, PCI_DEVICE_ID_NETMOS_9900, 0xA000, 0x2000), + .driver_data = netmos_9900 }, + { PCI_VDEVICE(NETMOS, PCI_DEVICE_ID_NETMOS_9705), + .driver_data = netmos_9705 }, + { PCI_VDEVICE(NETMOS, PCI_DEVICE_ID_NETMOS_9715), + .driver_data = netmos_9715 }, + { PCI_VDEVICE(NETMOS, PCI_DEVICE_ID_NETMOS_9755), + .driver_data = netmos_9755 }, + { PCI_VDEVICE(NETMOS, PCI_DEVICE_ID_NETMOS_9805), + .driver_data = netmos_9805 }, + { PCI_VDEVICE(NETMOS, PCI_DEVICE_ID_NETMOS_9815), + .driver_data = netmos_9815 }, + { PCI_VDEVICE_SUB(NETMOS, PCI_DEVICE_ID_NETMOS_9901, 0xA000, 0x2000), + .driver_data = netmos_9901 }, + { PCI_VDEVICE_SUB(NETMOS, PCI_DEVICE_ID_NETMOS_9865, 0xA000, 0x1000), + .driver_data = netmos_9865 }, + { PCI_VDEVICE_SUB(NETMOS, PCI_DEVICE_ID_NETMOS_9865, 0xA000, 0x2000), + .driver_data = netmos_9865 }, /* ASIX AX99100 PCIe to Multi I/O Controller */ - { PCI_VENDOR_ID_ASIX, PCI_DEVICE_ID_ASIX_AX99100, - 0xA000, 0x2000, 0, 0, asix_ax99100 }, + { PCI_VDEVICE_SUB(ASIX, PCI_DEVICE_ID_ASIX_AX99100, 0xA000, 0x2000), + .driver_data = asix_ax99100 }, /* Quatech SPPXP-100 Parallel port PCI ExpressCard */ - { PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_SPPXP_100, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, quatech_sppxp100 }, + { PCI_VDEVICE(QUATECH, PCI_DEVICE_ID_QUATECH_SPPXP_100), + .driver_data = quatech_sppxp100 }, /* WCH CH382L PCI-E single parallel port card */ - { 0x1c00, 0x3050, 0x1c00, 0x3050, 0, 0, wch_ch382l }, + { PCI_DEVICE_SUB(0x1c00, 0x3050, 0x1c00, 0x3050), + .driver_data = wch_ch382l }, /* Brainboxes IX-500/550 */ - { PCI_VENDOR_ID_INTASHIELD, 0x402a, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, oxsemi_pcie_pport }, + { PCI_VDEVICE(INTASHIELD, 0x402a), + .driver_data = oxsemi_pcie_pport }, /* Brainboxes UC-146/UC-157 */ - { PCI_VENDOR_ID_INTASHIELD, 0x0be1, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, brainboxes_uc146 }, - { PCI_VENDOR_ID_INTASHIELD, 0x0be2, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, brainboxes_uc146 }, + { PCI_VDEVICE(INTASHIELD, 0x0be1), + .driver_data = brainboxes_uc146 }, + { PCI_VDEVICE(INTASHIELD, 0x0be2), + .driver_data = brainboxes_uc146 }, /* Brainboxes PX-146/PX-257 */ - { PCI_VENDOR_ID_INTASHIELD, 0x401c, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, oxsemi_pcie_pport }, + { PCI_VDEVICE(INTASHIELD, 0x401c), + .driver_data = oxsemi_pcie_pport }, /* Brainboxes PX-203 */ - { PCI_VENDOR_ID_INTASHIELD, 0x4007, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, brainboxes_px203 }, + { PCI_VDEVICE(INTASHIELD, 0x4007), + .driver_data = brainboxes_px203 }, /* Brainboxes PX-475 */ - { PCI_VENDOR_ID_INTASHIELD, 0x401f, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, oxsemi_pcie_pport }, - { 0, } /* terminate list */ + { PCI_VDEVICE(INTASHIELD, 0x401f), + .driver_data = oxsemi_pcie_pport }, + { } /* terminate list */ }; MODULE_DEVICE_TABLE(pci, parport_pc_pci_tbl); diff --git a/drivers/parport/parport_serial.c b/drivers/parport/parport_serial.c index 24d4f3a3ec3d..4d4140c8584d 100644 --- a/drivers/parport/parport_serial.c +++ b/drivers/parport/parport_serial.c @@ -170,158 +170,176 @@ static struct parport_pc_pci cards[] = { static struct pci_device_id parport_serial_pci_tbl[] = { /* PCI cards */ - { PCI_VENDOR_ID_TITAN, PCI_DEVICE_ID_TITAN_110L, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, titan_110l }, - { PCI_VENDOR_ID_TITAN, PCI_DEVICE_ID_TITAN_210L, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, titan_210l }, - { PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9735, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, netmos_9xx5_combo }, - { PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9745, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, netmos_9xx5_combo }, - { PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9835, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, netmos_9xx5_combo }, - { PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9845, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, netmos_9xx5_combo }, - { PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9855, - 0x1000, 0x0020, 0, 0, netmos_9855_2p }, - { PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9855, - 0x1000, 0x0022, 0, 0, netmos_9855_2p }, - { PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9855, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, netmos_9855 }, - { PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9900, - 0xA000, 0x3011, 0, 0, netmos_9900 }, - { PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9900, - 0xA000, 0x3012, 0, 0, netmos_9900 }, - { PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9900, - 0xA000, 0x3020, 0, 0, netmos_9900_2p }, - { PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9912, - 0xA000, 0x2000, 0, 0, netmos_99xx_1p }, + { PCI_VDEVICE(TITAN, PCI_DEVICE_ID_TITAN_110L), + .driver_data = titan_110l }, + { PCI_VDEVICE(TITAN, PCI_DEVICE_ID_TITAN_210L), + .driver_data = titan_210l }, + { PCI_VDEVICE(NETMOS, PCI_DEVICE_ID_NETMOS_9735), + .driver_data = netmos_9xx5_combo }, + { PCI_VDEVICE(NETMOS, PCI_DEVICE_ID_NETMOS_9745), + .driver_data = netmos_9xx5_combo }, + { PCI_VDEVICE(NETMOS, PCI_DEVICE_ID_NETMOS_9835), + .driver_data = netmos_9xx5_combo }, + { PCI_VDEVICE(NETMOS, PCI_DEVICE_ID_NETMOS_9845), + .driver_data = netmos_9xx5_combo }, + { PCI_VDEVICE_SUB(NETMOS, PCI_DEVICE_ID_NETMOS_9855, 0x1000, 0x0020), + .driver_data = netmos_9855_2p }, + { PCI_VDEVICE_SUB(NETMOS, PCI_DEVICE_ID_NETMOS_9855, 0x1000, 0x0022), + .driver_data = netmos_9855_2p }, + { PCI_VDEVICE(NETMOS, PCI_DEVICE_ID_NETMOS_9855), + .driver_data = netmos_9855 }, + { PCI_VDEVICE_SUB(NETMOS, PCI_DEVICE_ID_NETMOS_9900, 0xA000, 0x3011), + .driver_data = netmos_9900 }, + { PCI_VDEVICE_SUB(NETMOS, PCI_DEVICE_ID_NETMOS_9900, 0xA000, 0x3012), + .driver_data = netmos_9900 }, + { PCI_VDEVICE_SUB(NETMOS, PCI_DEVICE_ID_NETMOS_9900, 0xA000, 0x3020), + .driver_data = netmos_9900_2p }, + { PCI_VDEVICE_SUB(NETMOS, PCI_DEVICE_ID_NETMOS_9912, 0xA000, 0x2000), + .driver_data = netmos_99xx_1p }, /* PCI_VENDOR_ID_AVLAB/Intek21 has another bunch of cards ...*/ - { PCI_VENDOR_ID_AFAVLAB, 0x2110, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, avlab_1s1p }, - { PCI_VENDOR_ID_AFAVLAB, 0x2111, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, avlab_1s1p }, - { PCI_VENDOR_ID_AFAVLAB, 0x2112, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, avlab_1s1p }, - { PCI_VENDOR_ID_AFAVLAB, 0x2140, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, avlab_1s2p }, - { PCI_VENDOR_ID_AFAVLAB, 0x2141, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, avlab_1s2p }, - { PCI_VENDOR_ID_AFAVLAB, 0x2142, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, avlab_1s2p }, - { PCI_VENDOR_ID_AFAVLAB, 0x2160, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, avlab_2s1p }, - { PCI_VENDOR_ID_AFAVLAB, 0x2161, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, avlab_2s1p }, - { PCI_VENDOR_ID_AFAVLAB, 0x2162, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, avlab_2s1p }, - { PCI_VENDOR_ID_SIIG, PCI_DEVICE_ID_SIIG_1S1P_10x_550, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, siig_1s1p_10x }, - { PCI_VENDOR_ID_SIIG, PCI_DEVICE_ID_SIIG_1S1P_10x_650, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, siig_1s1p_10x }, - { PCI_VENDOR_ID_SIIG, PCI_DEVICE_ID_SIIG_1S1P_10x_850, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, siig_1s1p_10x }, - { PCI_VENDOR_ID_SIIG, PCI_DEVICE_ID_SIIG_2S1P_10x_550, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, siig_2s1p_10x }, - { PCI_VENDOR_ID_SIIG, PCI_DEVICE_ID_SIIG_2S1P_10x_650, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, siig_2s1p_10x }, - { PCI_VENDOR_ID_SIIG, PCI_DEVICE_ID_SIIG_2S1P_10x_850, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, siig_2s1p_10x }, - { PCI_VENDOR_ID_SIIG, PCI_DEVICE_ID_SIIG_2P1S_20x_550, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, siig_2p1s_20x }, - { PCI_VENDOR_ID_SIIG, PCI_DEVICE_ID_SIIG_2P1S_20x_650, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, siig_2p1s_20x }, - { PCI_VENDOR_ID_SIIG, PCI_DEVICE_ID_SIIG_2P1S_20x_850, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, siig_2p1s_20x }, - { PCI_VENDOR_ID_SIIG, PCI_DEVICE_ID_SIIG_1S1P_20x_550, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, siig_2s1p_20x }, - { PCI_VENDOR_ID_SIIG, PCI_DEVICE_ID_SIIG_1S1P_20x_650, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, siig_1s1p_20x }, - { PCI_VENDOR_ID_SIIG, PCI_DEVICE_ID_SIIG_1S1P_20x_850, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, siig_1s1p_20x }, - { PCI_VENDOR_ID_SIIG, PCI_DEVICE_ID_SIIG_2S1P_20x_550, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, siig_2s1p_20x }, - { PCI_VENDOR_ID_SIIG, PCI_DEVICE_ID_SIIG_2S1P_20x_650, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, siig_2s1p_20x }, - { PCI_VENDOR_ID_SIIG, PCI_DEVICE_ID_SIIG_2S1P_20x_850, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, siig_2s1p_20x }, + { PCI_VDEVICE(AFAVLAB, 0x2110), + .driver_data = avlab_1s1p }, + { PCI_VDEVICE(AFAVLAB, 0x2111), + .driver_data = avlab_1s1p }, + { PCI_VDEVICE(AFAVLAB, 0x2112), + .driver_data = avlab_1s1p }, + { PCI_VDEVICE(AFAVLAB, 0x2140), + .driver_data = avlab_1s2p }, + { PCI_VDEVICE(AFAVLAB, 0x2141), + .driver_data = avlab_1s2p }, + { PCI_VDEVICE(AFAVLAB, 0x2142), + .driver_data = avlab_1s2p }, + { PCI_VDEVICE(AFAVLAB, 0x2160), + .driver_data = avlab_2s1p }, + { PCI_VDEVICE(AFAVLAB, 0x2161), + .driver_data = avlab_2s1p }, + { PCI_VDEVICE(AFAVLAB, 0x2162), + .driver_data = avlab_2s1p }, + { PCI_VDEVICE(SIIG, PCI_DEVICE_ID_SIIG_1S1P_10x_550), + .driver_data = siig_1s1p_10x }, + { PCI_VDEVICE(SIIG, PCI_DEVICE_ID_SIIG_1S1P_10x_650), + .driver_data = siig_1s1p_10x }, + { PCI_VDEVICE(SIIG, PCI_DEVICE_ID_SIIG_1S1P_10x_850), + .driver_data = siig_1s1p_10x }, + { PCI_VDEVICE(SIIG, PCI_DEVICE_ID_SIIG_2S1P_10x_550), + .driver_data = siig_2s1p_10x }, + { PCI_VDEVICE(SIIG, PCI_DEVICE_ID_SIIG_2S1P_10x_650), + .driver_data = siig_2s1p_10x }, + { PCI_VDEVICE(SIIG, PCI_DEVICE_ID_SIIG_2S1P_10x_850), + .driver_data = siig_2s1p_10x }, + { PCI_VDEVICE(SIIG, PCI_DEVICE_ID_SIIG_2P1S_20x_550), + .driver_data = siig_2p1s_20x }, + { PCI_VDEVICE(SIIG, PCI_DEVICE_ID_SIIG_2P1S_20x_650), + .driver_data = siig_2p1s_20x }, + { PCI_VDEVICE(SIIG, PCI_DEVICE_ID_SIIG_2P1S_20x_850), + .driver_data = siig_2p1s_20x }, + { PCI_VDEVICE(SIIG, PCI_DEVICE_ID_SIIG_1S1P_20x_550), + .driver_data = siig_2s1p_20x }, + { PCI_VDEVICE(SIIG, PCI_DEVICE_ID_SIIG_1S1P_20x_650), + .driver_data = siig_1s1p_20x }, + { PCI_VDEVICE(SIIG, PCI_DEVICE_ID_SIIG_1S1P_20x_850), + .driver_data = siig_1s1p_20x }, + { PCI_VDEVICE(SIIG, PCI_DEVICE_ID_SIIG_2S1P_20x_550), + .driver_data = siig_2s1p_20x }, + { PCI_VDEVICE(SIIG, PCI_DEVICE_ID_SIIG_2S1P_20x_650), + .driver_data = siig_2s1p_20x }, + { PCI_VDEVICE(SIIG, PCI_DEVICE_ID_SIIG_2S1P_20x_850), + .driver_data = siig_2s1p_20x }, /* PCI_VENDOR_ID_TIMEDIA/SUNIX has many differing cards ...*/ - { 0x1409, 0x7168, 0x1409, 0x4078, 0, 0, timedia_4078a }, - { 0x1409, 0x7168, 0x1409, 0x4079, 0, 0, timedia_4079h }, - { 0x1409, 0x7168, 0x1409, 0x4085, 0, 0, timedia_4085h }, - { 0x1409, 0x7168, 0x1409, 0x4088, 0, 0, timedia_4088a }, - { 0x1409, 0x7168, 0x1409, 0x4089, 0, 0, timedia_4089a }, - { 0x1409, 0x7168, 0x1409, 0x4095, 0, 0, timedia_4095a }, - { 0x1409, 0x7168, 0x1409, 0x4096, 0, 0, timedia_4096a }, - { 0x1409, 0x7168, 0x1409, 0x5078, 0, 0, timedia_4078u }, - { 0x1409, 0x7168, 0x1409, 0x5079, 0, 0, timedia_4079a }, - { 0x1409, 0x7168, 0x1409, 0x5085, 0, 0, timedia_4085u }, - { 0x1409, 0x7168, 0x1409, 0x6079, 0, 0, timedia_4079r }, - { 0x1409, 0x7168, 0x1409, 0x7079, 0, 0, timedia_4079s }, - { 0x1409, 0x7168, 0x1409, 0x8079, 0, 0, timedia_4079d }, - { 0x1409, 0x7168, 0x1409, 0x9079, 0, 0, timedia_4079e }, - { 0x1409, 0x7168, 0x1409, 0xa079, 0, 0, timedia_4079f }, - { 0x1409, 0x7168, 0x1409, 0xb079, 0, 0, timedia_9079a }, - { 0x1409, 0x7168, 0x1409, 0xc079, 0, 0, timedia_9079b }, - { 0x1409, 0x7168, 0x1409, 0xd079, 0, 0, timedia_9079c }, + { PCI_DEVICE_SUB(0x1409, 0x7168, 0x1409, 0x4078), + .driver_data = timedia_4078a }, + { PCI_DEVICE_SUB(0x1409, 0x7168, 0x1409, 0x4079), + .driver_data = timedia_4079h }, + { PCI_DEVICE_SUB(0x1409, 0x7168, 0x1409, 0x4085), + .driver_data = timedia_4085h }, + { PCI_DEVICE_SUB(0x1409, 0x7168, 0x1409, 0x4088), + .driver_data = timedia_4088a }, + { PCI_DEVICE_SUB(0x1409, 0x7168, 0x1409, 0x4089), + .driver_data = timedia_4089a }, + { PCI_DEVICE_SUB(0x1409, 0x7168, 0x1409, 0x4095), + .driver_data = timedia_4095a }, + { PCI_DEVICE_SUB(0x1409, 0x7168, 0x1409, 0x4096), + .driver_data = timedia_4096a }, + { PCI_DEVICE_SUB(0x1409, 0x7168, 0x1409, 0x5078), + .driver_data = timedia_4078u }, + { PCI_DEVICE_SUB(0x1409, 0x7168, 0x1409, 0x5079), + .driver_data = timedia_4079a }, + { PCI_DEVICE_SUB(0x1409, 0x7168, 0x1409, 0x5085), + .driver_data = timedia_4085u }, + { PCI_DEVICE_SUB(0x1409, 0x7168, 0x1409, 0x6079), + .driver_data = timedia_4079r }, + { PCI_DEVICE_SUB(0x1409, 0x7168, 0x1409, 0x7079), + .driver_data = timedia_4079s }, + { PCI_DEVICE_SUB(0x1409, 0x7168, 0x1409, 0x8079), + .driver_data = timedia_4079d }, + { PCI_DEVICE_SUB(0x1409, 0x7168, 0x1409, 0x9079), + .driver_data = timedia_4079e }, + { PCI_DEVICE_SUB(0x1409, 0x7168, 0x1409, 0xa079), + .driver_data = timedia_4079f }, + { PCI_DEVICE_SUB(0x1409, 0x7168, 0x1409, 0xb079), + .driver_data = timedia_9079a }, + { PCI_DEVICE_SUB(0x1409, 0x7168, 0x1409, 0xc079), + .driver_data = timedia_9079b }, + { PCI_DEVICE_SUB(0x1409, 0x7168, 0x1409, 0xd079), + .driver_data = timedia_9079c }, /* WCH CARDS */ - { PCI_VENDOR_ID_WCHCN, PCI_DEVICE_ID_WCHCN_CH353_1S1P, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, wch_ch353_1s1p }, - { PCI_VENDOR_ID_WCHCN, PCI_DEVICE_ID_WCHCN_CH353_2S1P, - 0x4348, 0x3253, 0, 0, wch_ch353_2s1p }, - { PCI_VENDOR_ID_WCHIC, PCI_DEVICE_ID_WCHIC_CH382_0S1P, - 0x1c00, 0x3050, 0, 0, wch_ch382_0s1p }, - { PCI_VENDOR_ID_WCHIC, PCI_DEVICE_ID_WCHIC_CH382_2S1P, - 0x1c00, 0x3250, 0, 0, wch_ch382_2s1p }, + { PCI_VDEVICE(WCHCN, PCI_DEVICE_ID_WCHCN_CH353_1S1P), + .driver_data = wch_ch353_1s1p }, + { PCI_VDEVICE_SUB(WCHCN, PCI_DEVICE_ID_WCHCN_CH353_2S1P, 0x4348, 0x3253), + .driver_data = wch_ch353_2s1p }, + { PCI_VDEVICE_SUB(WCHIC, PCI_DEVICE_ID_WCHIC_CH382_0S1P, 0x1c00, 0x3050), + .driver_data = wch_ch382_0s1p }, + { PCI_VDEVICE_SUB(WCHIC, PCI_DEVICE_ID_WCHIC_CH382_2S1P, 0x1c00, 0x3250), + .driver_data = wch_ch382_2s1p }, /* BrainBoxes PX272/PX306 MIO card */ - { PCI_VENDOR_ID_INTASHIELD, 0x4100, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, brainboxes_5s1p }, + { PCI_VDEVICE(INTASHIELD, 0x4100), + .driver_data = brainboxes_5s1p }, /* Sunix boards */ - { PCI_VENDOR_ID_SUNIX, PCI_DEVICE_ID_SUNIX_1999, PCI_VENDOR_ID_SUNIX, - 0x0100, 0, 0, sunix_4008a }, - { PCI_VENDOR_ID_SUNIX, PCI_DEVICE_ID_SUNIX_1999, PCI_VENDOR_ID_SUNIX, - 0x0101, 0, 0, sunix_5069a }, - { PCI_VENDOR_ID_SUNIX, PCI_DEVICE_ID_SUNIX_1999, PCI_VENDOR_ID_SUNIX, - 0x0102, 0, 0, sunix_5079a }, - { PCI_VENDOR_ID_SUNIX, PCI_DEVICE_ID_SUNIX_1999, PCI_VENDOR_ID_SUNIX, - 0x0104, 0, 0, sunix_5099a }, + { PCI_VDEVICE_SUB(SUNIX, PCI_DEVICE_ID_SUNIX_1999, PCI_VENDOR_ID_SUNIX, 0x0100), + .driver_data = sunix_4008a }, + { PCI_VDEVICE_SUB(SUNIX, PCI_DEVICE_ID_SUNIX_1999, PCI_VENDOR_ID_SUNIX, 0x0101), + .driver_data = sunix_5069a }, + { PCI_VDEVICE_SUB(SUNIX, PCI_DEVICE_ID_SUNIX_1999, PCI_VENDOR_ID_SUNIX, 0x0102), + .driver_data = sunix_5079a }, + { PCI_VDEVICE_SUB(SUNIX, PCI_DEVICE_ID_SUNIX_1999, PCI_VENDOR_ID_SUNIX, 0x0104), + .driver_data = sunix_5099a }, /* Brainboxes UC-203 */ - { PCI_VENDOR_ID_INTASHIELD, 0x0bc1, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, brainboxes_uc257 }, - { PCI_VENDOR_ID_INTASHIELD, 0x0bc2, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, brainboxes_uc257 }, + { PCI_VDEVICE(INTASHIELD, 0x0bc1), + .driver_data = brainboxes_uc257 }, + { PCI_VDEVICE(INTASHIELD, 0x0bc2), + .driver_data = brainboxes_uc257 }, /* Brainboxes UC-257 */ - { PCI_VENDOR_ID_INTASHIELD, 0x0861, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, brainboxes_uc257 }, - { PCI_VENDOR_ID_INTASHIELD, 0x0862, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, brainboxes_uc257 }, - { PCI_VENDOR_ID_INTASHIELD, 0x0863, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, brainboxes_uc257 }, + { PCI_VDEVICE(INTASHIELD, 0x0861), + .driver_data = brainboxes_uc257 }, + { PCI_VDEVICE(INTASHIELD, 0x0862), + .driver_data = brainboxes_uc257 }, + { PCI_VDEVICE(INTASHIELD, 0x0863), + .driver_data = brainboxes_uc257 }, /* Brainboxes UC-414 */ - { PCI_VENDOR_ID_INTASHIELD, 0x0e61, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, brainboxes_uc414 }, + { PCI_VDEVICE(INTASHIELD, 0x0e61), + .driver_data = brainboxes_uc414 }, /* Brainboxes UC-475 */ - { PCI_VENDOR_ID_INTASHIELD, 0x0981, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, brainboxes_uc257 }, - { PCI_VENDOR_ID_INTASHIELD, 0x0982, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, brainboxes_uc257 }, + { PCI_VDEVICE(INTASHIELD, 0x0981), + .driver_data = brainboxes_uc257 }, + { PCI_VDEVICE(INTASHIELD, 0x0982), + .driver_data = brainboxes_uc257 }, /* Brainboxes IS-300/IS-500 */ - { PCI_VENDOR_ID_INTASHIELD, 0x0da0, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, brainboxes_is300 }, + { PCI_VDEVICE(INTASHIELD, 0x0da0), + .driver_data = brainboxes_is300 }, /* Brainboxes PX-263/PX-295 */ - { PCI_VENDOR_ID_INTASHIELD, 0x402c, - PCI_ANY_ID, PCI_ANY_ID, 0, 0, brainboxes_px263 }, + { PCI_VDEVICE(INTASHIELD, 0x402c), + .driver_data = brainboxes_px263 }, - { 0, } /* terminate list */ + { } /* terminate list */ }; MODULE_DEVICE_TABLE(pci,parport_serial_pci_tbl); From 67b6fc084b034a91c3ec7907a3fed89a2450f30b Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Tue, 30 Jun 2026 15:27:14 -0400 Subject: [PATCH 021/135] uio: Fix stale info pointer in failed registration path After device_add(), the UIO device is visible to userspace and /dev/uioX can be opened. If a later setup step fails, __uio_register_device() unwinds the device but leaves idev->info pointing at the caller-owned struct uio_info. That is unsafe when an opener races with the failed registration path. The open file keeps a reference to the uio_device, while the caller sees registration failure and may free its struct uio_info. Later file operations can then follow idev->info and dereference freed memory. Handle post-device_add() failures like unregister: remove UIO attributes while the info pointer is still valid, then clear idev->info under info_lock and wake existing waiters/async users before removing the device and minor. This makes already-open file descriptors observe the same "device gone" state as normal uio_unregister_device(). Fixes: a93e7b331568 ("uio: Prevent device destruction while fds are open") Signed-off-by: Yuho Choi Link: https://patch.msgid.link/20260630192714.1867170-1-dbgh9129@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/uio/uio.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c index 1e4ade78ed84..e77d5e7d5f64 100644 --- a/drivers/uio/uio.c +++ b/drivers/uio/uio.c @@ -1057,6 +1057,11 @@ int __uio_register_device(struct module *owner, err_request_irq: uio_dev_del_attributes(idev); err_uio_dev_add_attributes: + mutex_lock(&idev->info_lock); + idev->info = NULL; + mutex_unlock(&idev->info_lock); + wake_up_interruptible(&idev->wait); + kill_fasync(&idev->async_queue, SIGIO, POLL_HUP); device_del(&idev->dev); err_device_create: uio_free_minor(idev->minor); From a432f68c51fc6761658445e80a798b8425fdb3dc Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 4 Jul 2026 23:26:42 +0800 Subject: [PATCH 022/135] uio: sercos3: add missing MODULE_DEVICE_TABLE() The driver has a match table for the pci bus wired into its driver structure, but the table is not exported with MODULE_DEVICE_TABLE(). Add the missing MODULE_DEVICE_TABLE() entry so module alias information is generated for automatic module loading. This is a source-level fix. It does not claim dynamic hardware reproduction; the evidence is the driver-owned match table, its use by the driver registration structure, and the missing module alias publication. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260704152642.54769-1-pengpeng@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/uio/uio_sercos3.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/uio/uio_sercos3.c b/drivers/uio/uio_sercos3.c index 12afc2fa1a0b..80087897f9b8 100644 --- a/drivers/uio/uio_sercos3.c +++ b/drivers/uio/uio_sercos3.c @@ -212,6 +212,7 @@ static const struct pci_device_id sercos3_pci_ids[] = { }, { 0, } }; +MODULE_DEVICE_TABLE(pci, sercos3_pci_ids); static struct pci_driver sercos3_pci_driver = { .name = "sercos3", From a477830bc2d615fa0dfd52508d8c94cec6721299 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Tue, 14 Jul 2026 19:56:24 +0100 Subject: [PATCH 023/135] uio: make read-only const array porttypes static Don't populate the read-only const array porttypes on the stack at run time, instead make it static const char * const Signed-off-by: Colin Ian King Link: https://patch.msgid.link/20260714185624.192829-1-colin.i.king@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/uio/uio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c index e77d5e7d5f64..f8fa20522660 100644 --- a/drivers/uio/uio.c +++ b/drivers/uio/uio.c @@ -150,7 +150,7 @@ static ssize_t portio_size_show(struct uio_port *port, char *buf) static ssize_t portio_porttype_show(struct uio_port *port, char *buf) { - const char *porttypes[] = {"none", "x86", "gpio", "other"}; + static const char * const porttypes[] = {"none", "x86", "gpio", "other"}; if ((port->porttype < 0) || (port->porttype > UIO_PORT_OTHER)) return -EINVAL; From 3adfac7a7494253c05b2a76508aec52dd0894ddf Mon Sep 17 00:00:00 2001 From: Li zeming Date: Mon, 1 Jun 2026 01:07:50 +0200 Subject: [PATCH 024/135] accessibility/speakup/speakup_acnt: Add header file macro definition I think the header file could avoid redefinition errors. at compile time by adding macro definitions. Signed-off-by: Li zeming Signed-off-by: Samuel Thibault Reviewed-by: Samuel Thibault Link: https://patch.msgid.link/20260531230804.254962-2-samuel.thibault@ens-lyon.org Signed-off-by: Greg Kroah-Hartman --- drivers/accessibility/speakup/speakup_acnt.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/accessibility/speakup/speakup_acnt.h b/drivers/accessibility/speakup/speakup_acnt.h index cffa938ae580..cea05d770f6d 100644 --- a/drivers/accessibility/speakup/speakup_acnt.h +++ b/drivers/accessibility/speakup/speakup_acnt.h @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* speakup_acntpc.h - header file for speakups Accent-PC driver. */ +#ifndef _SPEAKUP_ACNT_H +#define _SPEAKUP_ACNT_H #define SYNTH_IO_EXTENT 0x02 @@ -17,3 +19,4 @@ #define SYNTH_FULL 'F' /* synth is full. */ #define SYNTH_ALMOST_EMPTY 'M' /* synth has less than 2 seconds of text left */ #define SYNTH_SPEAKING 's' /* synth is speaking and has a fare way to go */ +#endif From 9601f59a1966f12c0d4652e32663f3553f20a9e9 Mon Sep 17 00:00:00 2001 From: Li zeming Date: Mon, 1 Jun 2026 01:07:51 +0200 Subject: [PATCH 025/135] accessibility/speakup/speakup_dtlk: Add header file macro definition Add header file macro definition. Signed-off-by: Li zeming Signed-off-by: Samuel Thibault Reviewed-by: Samuel Thibault Link: https://patch.msgid.link/20260531230804.254962-3-samuel.thibault@ens-lyon.org Signed-off-by: Greg Kroah-Hartman --- drivers/accessibility/speakup/speakup_dtlk.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/accessibility/speakup/speakup_dtlk.h b/drivers/accessibility/speakup/speakup_dtlk.h index 9c378b58066e..101848edec2e 100644 --- a/drivers/accessibility/speakup/speakup_dtlk.h +++ b/drivers/accessibility/speakup/speakup_dtlk.h @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* speakup_dtlk.h - header file for speakups DoubleTalk driver. */ +#ifndef _SPEAKUP_DTLK_H +#define _SPEAKUP_DTLK_H #define SYNTH_IO_EXTENT 0x02 #define SYNTH_CLEAR 0x18 /* stops speech */ @@ -61,3 +63,4 @@ struct synth_settings { */ u_char has_indexing; /* nonzero if indexing is implemented */ }; +#endif From fe58bfdd013ed84bd308321fac8f54c8ec4b823c Mon Sep 17 00:00:00 2001 From: Xu Panda Date: Mon, 1 Jun 2026 01:07:52 +0200 Subject: [PATCH 026/135] speakup/utils: use "!P" instead of "P == 0" comparing pointer to 0, use !P instead of it. Reported-by: Zeal Robot Signed-off-by: Xu Panda Signed-off-by: Samuel Thibault Reviewed-by: Samuel Thibault Link: https://patch.msgid.link/20260531230804.254962-4-samuel.thibault@ens-lyon.org Signed-off-by: Greg Kroah-Hartman --- drivers/accessibility/speakup/utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/accessibility/speakup/utils.h b/drivers/accessibility/speakup/utils.h index 4ce9a12f7664..db00c962f8e2 100644 --- a/drivers/accessibility/speakup/utils.h +++ b/drivers/accessibility/speakup/utils.h @@ -36,7 +36,7 @@ static inline void open_input(const char *dir_name, const char *name) else snprintf(filename, sizeof(filename), "%s", name); infile = fopen(filename, "r"); - if (infile == 0) { + if (!infile) { fprintf(stderr, "can't open %s\n", filename); exit(1); } From bce0e640623372520d9d90c42f33ddbfb576ce69 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Mon, 1 Jun 2026 01:07:53 +0200 Subject: [PATCH 027/135] accessibility: speakup: Fix incorrect string length computation in report_char_chartab_status() snprintf() returns the "number of characters which *would* be generated for the given input", not the size *really* generated. In order to avoid too large values for 'len' (and potential negative values for "sizeof(buf) - (len - 1)") use scnprintf() instead of snprintf(). Fixes: c6e3fd22cd53 ("Staging: add speakup to the staging directory") Signed-off-by: Christophe JAILLET Signed-off-by: Samuel Thibault Reviewed-by: Samuel Thibault Reviewed-by: Dan Carpenter Link: https://patch.msgid.link/20260531230804.254962-5-samuel.thibault@ens-lyon.org Signed-off-by: Greg Kroah-Hartman --- drivers/accessibility/speakup/kobjects.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/accessibility/speakup/kobjects.c b/drivers/accessibility/speakup/kobjects.c index 0dfdb6608e02..943ef71b1329 100644 --- a/drivers/accessibility/speakup/kobjects.c +++ b/drivers/accessibility/speakup/kobjects.c @@ -92,9 +92,9 @@ static void report_char_chartab_status(int reset, int received, int used, if (reset) { pr_info("%s reset to defaults\n", object_type[do_characters]); } else if (received) { - len = snprintf(buf, sizeof(buf), - " updated %d of %d %s\n", - used, received, object_type[do_characters]); + len = scnprintf(buf, sizeof(buf), + " updated %d of %d %s\n", + used, received, object_type[do_characters]); if (rejected) snprintf(buf + (len - 1), sizeof(buf) - (len - 1), " with %d reject%s\n", From d59f36095e115315cdb14ef1f8a56c55f5d36243 Mon Sep 17 00:00:00 2001 From: bajing Date: Mon, 1 Jun 2026 01:07:54 +0200 Subject: [PATCH 028/135] speakup: genmap: remove redundant post-increment In the while loop, the variable lc is unused and is reinitialized later, so this redundant operation should be removed. Signed-off-by: bajing Signed-off-by: Samuel Thibault Reviewed-by: Samuel Thibault Link: https://patch.msgid.link/20260531230804.254962-6-samuel.thibault@ens-lyon.org Signed-off-by: Greg Kroah-Hartman --- drivers/accessibility/speakup/genmap.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/accessibility/speakup/genmap.c b/drivers/accessibility/speakup/genmap.c index 0882bab10fb8..8a5125db471e 100644 --- a/drivers/accessibility/speakup/genmap.c +++ b/drivers/accessibility/speakup/genmap.c @@ -71,7 +71,6 @@ main(int argc, char *argv[]) open_input(NULL, argv[1]); while (fgets(buffer, sizeof(buffer), infile)) { - lc++; value = shift_state = 0; cp = strtok(buffer, delims); From 2d2e326af3df23ed926b19f74454aaf00ad0eaed Mon Sep 17 00:00:00 2001 From: liujing Date: Mon, 1 Jun 2026 01:07:55 +0200 Subject: [PATCH 029/135] speakup: Fix the wrong format specifier Make a minor change to eliminate a static checker warning. The type of '(unsigned int)kp[i]' is unsigned int, so the correct format specifier should be %u instead of %d. Signed-off-by: liujing Signed-off-by: Samuel Thibault Reviewed-by: Samuel Thibault Link: https://patch.msgid.link/20260531230804.254962-7-samuel.thibault@ens-lyon.org Signed-off-by: Greg Kroah-Hartman --- drivers/accessibility/speakup/genmap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/accessibility/speakup/genmap.c b/drivers/accessibility/speakup/genmap.c index 8a5125db471e..a9e308bdd0be 100644 --- a/drivers/accessibility/speakup/genmap.c +++ b/drivers/accessibility/speakup/genmap.c @@ -152,7 +152,7 @@ main(int argc, char *argv[]) continue; printf("\n\t%d,", lc); for (i = 0; i < max_states; i++) - printf(" %d,", (unsigned int)kp[i]); + printf(" %u,", (unsigned int)kp[i]); } printf("\n\t0, %d\n", map_ver); From 5c3949ef38c86822dd18bef38a0e970052ebc27c Mon Sep 17 00:00:00 2001 From: Jagadeesh Yalapalli Date: Mon, 1 Jun 2026 01:07:56 +0200 Subject: [PATCH 030/135] speakup: Standardize character attribute types to u16 This change replaces non-portable `u_short` types with standardized `u16` throughout the speakup subsystem to ensure: 1. Consistent 16-bit width across all architectures. 2. Improved code portability and readability. 3. Elimination of platform-dependent type sizes. 4. Safe bitwise operations without sign-extension risks. Signed-off-by: Jagadeesh Yalapalli Signed-off-by: Samuel Thibault Reviewed-by: Samuel Thibault Link: https://patch.msgid.link/20260531230804.254962-8-samuel.thibault@ens-lyon.org Signed-off-by: Greg Kroah-Hartman --- drivers/accessibility/speakup/keyhelp.c | 10 +-- drivers/accessibility/speakup/kobjects.c | 4 +- drivers/accessibility/speakup/main.c | 74 ++++++++++---------- drivers/accessibility/speakup/selection.c | 2 +- drivers/accessibility/speakup/speakup.h | 10 +-- drivers/accessibility/speakup/speakup_dtlk.h | 2 +- drivers/accessibility/speakup/spk_types.h | 2 +- drivers/accessibility/speakup/synth.c | 2 +- 8 files changed, 53 insertions(+), 53 deletions(-) diff --git a/drivers/accessibility/speakup/keyhelp.c b/drivers/accessibility/speakup/keyhelp.c index 822ceac83068..9c6e488adc2a 100644 --- a/drivers/accessibility/speakup/keyhelp.c +++ b/drivers/accessibility/speakup/keyhelp.c @@ -14,8 +14,8 @@ #define MAXFUNCS 130 #define MAXKEYS 256 static const int num_key_names = MSG_KEYNAMES_END - MSG_KEYNAMES_START + 1; -static u_short key_offsets[MAXFUNCS], key_data[MAXKEYS]; -static u_short masks[] = { 32, 16, 8, 4, 2, 1 }; +static u16 key_offsets[MAXFUNCS], key_data[MAXKEYS]; +static u16 masks[] = { 32, 16, 8, 4, 2, 1 }; static short letter_offsets[26] = { -1, -1, -1, -1, -1, -1, -1, -1, @@ -49,7 +49,7 @@ static int cur_item, nstates; static void build_key_data(void) { u_char *kp, counters[MAXFUNCS], ch, ch1; - u_short *p_key, key; + u16 *p_key, key; int i, offset = 1; nstates = (int)(state_tbl[-1]); @@ -129,12 +129,12 @@ static int help_init(void) return 0; } -int spk_handle_help(struct vc_data *vc, u_char type, u_char ch, u_short key) +int spk_handle_help(struct vc_data *vc, u_char type, u_char ch, u16 key) { int i, n; char *name; u_char func, *kp; - u_short *p_keys, val; + u16 *p_keys, val; if (letter_offsets[0] == -1) help_init(); diff --git a/drivers/accessibility/speakup/kobjects.c b/drivers/accessibility/speakup/kobjects.c index 943ef71b1329..9ff7a4c680db 100644 --- a/drivers/accessibility/speakup/kobjects.c +++ b/drivers/accessibility/speakup/kobjects.c @@ -120,7 +120,7 @@ static ssize_t chars_chartab_store(struct kobject *kobj, ssize_t retval = count; unsigned long flags; unsigned long index = 0; - int charclass = 0; + u16 charclass = 0; int received = 0; int used = 0; int rejected = 0; @@ -461,7 +461,7 @@ static ssize_t punc_show(struct kobject *kobj, struct kobj_attribute *attr, struct st_var_header *p_header; struct punc_var_t *var; struct st_bits_data *pb; - short mask; + u16 mask; unsigned long flags; p_header = spk_var_header_by_name(attr->attr.name); diff --git a/drivers/accessibility/speakup/main.c b/drivers/accessibility/speakup/main.c index 78a77dd789a2..0962741a2ca2 100644 --- a/drivers/accessibility/speakup/main.c +++ b/drivers/accessibility/speakup/main.c @@ -63,7 +63,7 @@ int spk_attrib_bleep, spk_bleeps, spk_bleep_time = 10; int spk_no_intr, spk_spell_delay; int spk_key_echo, spk_say_word_ctl; int spk_say_ctrl, spk_bell_pos; -short spk_punc_mask; +u16 spk_punc_mask; int spk_punc_level, spk_reading_punc; int spk_cur_phonetic; char spk_str_caps_start[MAXVARLEN + 1] = "\0"; @@ -183,13 +183,13 @@ char *spk_default_chars[256] = { /* 251 */ "u circumflex", "u oomlaut", "y acute", "thorn", "y oomlaut" }; -/* array of 256 u_short (one for each character) +/* array of 256 u16 (one for each character) * initialized to default_chartab and user selectable via * /sys/module/speakup/parameters/chartab */ -u_short spk_chartab[256]; +u16 spk_chartab[256]; -static u_short default_chartab[256] = { +static u16 default_chartab[256] = { B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, /* 0-7 */ B_CTL, B_CTL, A_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, /* 8-15 */ B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, /*16-23 */ @@ -267,10 +267,10 @@ static void speakup_date(struct vc_data *vc) spk_y = spk_cy = vc->state.y; spk_pos = spk_cp = vc->vc_pos; spk_old_attr = spk_attr; - spk_attr = get_attributes(vc, (u_short *)spk_pos); + spk_attr = get_attributes(vc, (u16 *)spk_pos); } -static void bleep(u_short val) +static void bleep(u16 val) { static const short vals[] = { 350, 370, 392, 414, 440, 466, 491, 523, 554, 587, 619, 659 @@ -346,14 +346,14 @@ static void speakup_cut(struct vc_data *vc) if (!mark_cut_flag) { mark_cut_flag = 1; - spk_xs = (u_short)spk_x; - spk_ys = (u_short)spk_y; + spk_xs = (u16)spk_x; + spk_ys = (u16)spk_y; spk_sel_cons = vc; synth_printf("%s\n", spk_msg_get(MSG_MARK)); return; } - spk_xe = (u_short)spk_x; - spk_ye = (u_short)spk_y; + spk_xe = (u16)spk_x; + spk_ye = (u16)spk_y; mark_cut_flag = 0; synth_printf("%s\n", spk_msg_get(MSG_CUT)); @@ -482,7 +482,7 @@ static void say_char(struct vc_data *vc) u16 ch; spk_old_attr = spk_attr; - ch = get_char(vc, (u_short *)spk_pos, &spk_attr); + ch = get_char(vc, (u16 *)spk_pos, &spk_attr); if (spk_attr != spk_old_attr) { if (spk_attrib_bleep & 1) bleep(spk_y); @@ -497,7 +497,7 @@ static void say_phonetic_char(struct vc_data *vc) u16 ch; spk_old_attr = spk_attr; - ch = get_char(vc, (u_short *)spk_pos, &spk_attr); + ch = get_char(vc, (u16 *)spk_pos, &spk_attr); if (ch <= 0x7f && isalpha(ch)) { ch &= 0x1f; synth_printf("%s\n", phonetic[--ch]); @@ -549,7 +549,7 @@ static u_long get_word(struct vc_data *vc) u_char temp; spk_old_attr = spk_attr; - ch = get_char(vc, (u_short *)tmp_pos, &temp); + ch = get_char(vc, (u16 *)tmp_pos, &temp); /* decided to take out the sayword if on a space (mis-information */ if (spk_say_word_ctl && ch == SPACE) { @@ -558,26 +558,26 @@ static u_long get_word(struct vc_data *vc) return 0; } else if (tmpx < vc->vc_cols - 2 && (ch == SPACE || ch == 0 || (ch < 0x100 && IS_WDLM(ch))) && - get_char(vc, (u_short *)tmp_pos + 1, &temp) > SPACE) { + get_char(vc, (u16 *)tmp_pos + 1, &temp) > SPACE) { tmp_pos += 2; tmpx++; } else { while (tmpx > 0) { - ch = get_char(vc, (u_short *)tmp_pos - 1, &temp); + ch = get_char(vc, (u16 *)tmp_pos - 1, &temp); if ((ch == SPACE || ch == 0 || (ch < 0x100 && IS_WDLM(ch))) && - get_char(vc, (u_short *)tmp_pos, &temp) > SPACE) + get_char(vc, (u16 *)tmp_pos, &temp) > SPACE) break; tmp_pos -= 2; tmpx--; } } - attr_ch = get_char(vc, (u_short *)tmp_pos, &spk_attr); + attr_ch = get_char(vc, (u16 *)tmp_pos, &spk_attr); buf[cnt++] = attr_ch; while (tmpx < vc->vc_cols - 1 && cnt < ARRAY_SIZE(buf) - 1) { tmp_pos += 2; tmpx++; - ch = get_char(vc, (u_short *)tmp_pos, &temp); + ch = get_char(vc, (u16 *)tmp_pos, &temp); if (ch == SPACE || ch == 0 || (buf[cnt - 1] < 0x100 && IS_WDLM(buf[cnt - 1]) && ch > SPACE)) @@ -591,7 +591,7 @@ static u_long get_word(struct vc_data *vc) static void say_word(struct vc_data *vc) { u_long cnt = get_word(vc); - u_short saved_punc_mask = spk_punc_mask; + u16 saved_punc_mask = spk_punc_mask; if (cnt == 0) return; @@ -606,7 +606,7 @@ static void say_prev_word(struct vc_data *vc) u_char temp; u16 ch; enum edge edge_said = edge_none; - u_short last_state = 0, state = 0; + u16 last_state = 0, state = 0; spk_parked |= 0x01; @@ -635,7 +635,7 @@ static void say_prev_word(struct vc_data *vc) spk_x--; } spk_pos -= 2; - ch = get_char(vc, (u_short *)spk_pos, &temp); + ch = get_char(vc, (u16 *)spk_pos, &temp); if (ch == SPACE || ch == 0) state = 0; else if (ch < 0x100 && IS_WDLM(ch)) @@ -661,7 +661,7 @@ static void say_next_word(struct vc_data *vc) u_char temp; u16 ch; enum edge edge_said = edge_none; - u_short last_state = 2, state = 0; + u16 last_state = 2, state = 0; spk_parked |= 0x01; if (spk_x == vc->vc_cols - 1 && spk_y == vc->vc_rows - 1) { @@ -669,7 +669,7 @@ static void say_next_word(struct vc_data *vc) return; } while (1) { - ch = get_char(vc, (u_short *)spk_pos, &temp); + ch = get_char(vc, (u16 *)spk_pos, &temp); if (ch == SPACE || ch == 0) state = 0; else if (ch < 0x100 && IS_WDLM(ch)) @@ -755,9 +755,9 @@ static int get_line(struct vc_data *vc) u_char tmp2; spk_old_attr = spk_attr; - spk_attr = get_attributes(vc, (u_short *)spk_pos); + spk_attr = get_attributes(vc, (u16 *)spk_pos); for (i = 0; i < vc->vc_cols; i++) { - buf[i] = get_char(vc, (u_short *)tmp, &tmp2); + buf[i] = get_char(vc, (u16 *)tmp, &tmp2); tmp += 2; } for (--i; i >= 0; i--) @@ -770,7 +770,7 @@ static void say_line(struct vc_data *vc) { int i = get_line(vc); u16 *cp; - u_short saved_punc_mask = spk_punc_mask; + u16 saved_punc_mask = spk_punc_mask; if (i == 0) { synth_printf("%s\n", spk_msg_get(MSG_BLANK)); @@ -817,12 +817,12 @@ static int say_from_to(struct vc_data *vc, u_long from, u_long to, { int i = 0; u_char tmp; - u_short saved_punc_mask = spk_punc_mask; + u16 saved_punc_mask = spk_punc_mask; spk_old_attr = spk_attr; - spk_attr = get_attributes(vc, (u_short *)from); + spk_attr = get_attributes(vc, (u16 *)from); while (from < to) { - buf[i++] = get_char(vc, (u_short *)from, &tmp); + buf[i++] = get_char(vc, (u16 *)from, &tmp); from += 2; if (i >= vc->vc_size_row) break; @@ -895,10 +895,10 @@ static int get_sentence_buf(struct vc_data *vc, int read_punc) sentmarks[bn][0] = &sentbuf[bn][0]; i = 0; spk_old_attr = spk_attr; - spk_attr = get_attributes(vc, (u_short *)start); + spk_attr = get_attributes(vc, (u16 *)start); while (start < end) { - sentbuf[bn][i] = get_char(vc, (u_short *)start, &tmp); + sentbuf[bn][i] = get_char(vc, (u16 *)start, &tmp); if (i > 0) { if (sentbuf[bn][i] == SPACE && sentbuf[bn][i - 1] == '.' && @@ -1047,7 +1047,7 @@ static void say_position(struct vc_data *vc) static void say_char_num(struct vc_data *vc) { u_char tmp; - u16 ch = get_char(vc, (u_short *)spk_pos, &tmp); + u16 ch = get_char(vc, (u16 *)spk_pos, &tmp); synth_printf(spk_msg_get(MSG_CHAR_INFO), ch, ch); } @@ -1080,7 +1080,7 @@ static void spkup_write(const u16 *in_buf, int count) { static int rep_count; static u16 ch = '\0', old_ch = '\0'; - static u_short char_type, last_type; + static u16 char_type, last_type; int in_count = count; spk_keydown = 0; @@ -1325,9 +1325,9 @@ void spk_reset_default_chartab(void) static const struct st_bits_data *pb_edit; -static int edit_bits(struct vc_data *vc, u_char type, u_char ch, u_short key) +static int edit_bits(struct vc_data *vc, u_char type, u_char ch, u16 key) { - short mask = pb_edit->mask, ch_type = spk_chartab[ch]; + u16 mask = pb_edit->mask, ch_type = spk_chartab[ch]; if (type != KT_LATIN || (ch_type & B_NUM) || ch < SPACE) return -1; @@ -1947,7 +1947,7 @@ static void speakup_bits(struct vc_data *vc) spk_special_handler = edit_bits; } -static int handle_goto(struct vc_data *vc, u_char type, u_char ch, u_short key) +static int handle_goto(struct vc_data *vc, u_char type, u_char ch, u16 key) { static u_char goto_buf[8]; static int num; @@ -2105,7 +2105,7 @@ static void do_spkup(struct vc_data *vc, u_char value) static const char *pad_chars = "0123456789+-*/\015,.?()"; static int -speakup_key(struct vc_data *vc, int shift_state, int keycode, u_short keysym, +speakup_key(struct vc_data *vc, int shift_state, int keycode, u16 keysym, int up_flag) { unsigned long flags; diff --git a/drivers/accessibility/speakup/selection.c b/drivers/accessibility/speakup/selection.c index 7df7afad5ab4..1713ce4e0ba5 100644 --- a/drivers/accessibility/speakup/selection.c +++ b/drivers/accessibility/speakup/selection.c @@ -13,7 +13,7 @@ #include "speakup.h" -unsigned short spk_xs, spk_ys, spk_xe, spk_ye; /* our region points */ +u16 spk_xs, spk_ys, spk_xe, spk_ye; /* our region points */ struct vc_data *spk_sel_cons; struct speakup_selection_work { diff --git a/drivers/accessibility/speakup/speakup.h b/drivers/accessibility/speakup/speakup.h index 54f1226ea061..984a729fd82d 100644 --- a/drivers/accessibility/speakup/speakup.h +++ b/drivers/accessibility/speakup/speakup.h @@ -62,7 +62,7 @@ int spk_set_num_var(int val, struct st_var_header *var, int how); int spk_set_string_var(const char *page, struct st_var_header *var, int len); int spk_set_mask_bits(const char *input, const int which, const int how); extern special_func spk_special_handler; -int spk_handle_help(struct vc_data *vc, u_char type, u_char ch, u_short key); +int spk_handle_help(struct vc_data *vc, u_char type, u_char ch, u16 key); int synth_init(char *name); void synth_release(void); @@ -82,7 +82,7 @@ void synth_writeu(const char *buf, size_t count); int synth_supports_indexing(void); extern struct vc_data *spk_sel_cons; -extern unsigned short spk_xs, spk_ys, spk_xe, spk_ye; /* our region points */ +extern u16 spk_xs, spk_ys, spk_xe, spk_ye; /* our region points */ extern wait_queue_head_t speakup_event; extern struct kobject *speakup_kobj; @@ -95,20 +95,20 @@ extern struct st_spk_t *speakup_console[]; extern struct spk_synth *synth; extern char spk_pitch_buff[]; extern u_char *spk_our_keys[]; -extern short spk_punc_masks[]; +extern u16 spk_punc_masks[]; extern char spk_str_caps_start[], spk_str_caps_stop[], spk_str_pause[]; extern bool spk_paused; extern const struct st_bits_data spk_punc_info[]; extern u_char spk_key_buf[600]; extern char *spk_characters[]; extern char *spk_default_chars[]; -extern u_short spk_chartab[]; +extern u16 spk_chartab[]; extern int spk_no_intr, spk_say_ctrl, spk_say_word_ctl, spk_punc_level; extern int spk_reading_punc, spk_attrib_bleep, spk_bleeps; extern int spk_bleep_time, spk_bell_pos; extern int spk_spell_delay, spk_key_echo; extern int spk_cur_phonetic; -extern short spk_punc_mask; +extern u16 spk_punc_mask; extern short spk_pitch_shift, synth_flags; extern bool spk_quiet_boot; extern char *synth_name; diff --git a/drivers/accessibility/speakup/speakup_dtlk.h b/drivers/accessibility/speakup/speakup_dtlk.h index 101848edec2e..9354e3382bba 100644 --- a/drivers/accessibility/speakup/speakup_dtlk.h +++ b/drivers/accessibility/speakup/speakup_dtlk.h @@ -41,7 +41,7 @@ /* data returned by Interrogate command */ struct synth_settings { - u_short serial_number; /* 0-7Fh:0-7Fh */ + u16 serial_number; /* 0-7Fh:0-7Fh */ u_char rom_version[24]; /* null terminated string */ u_char mode; /* 0=Character; 1=Phoneme; 2=Text */ u_char punc_level; /* nB; 0-7 */ diff --git a/drivers/accessibility/speakup/spk_types.h b/drivers/accessibility/speakup/spk_types.h index 08011518a28a..a5762330e249 100644 --- a/drivers/accessibility/speakup/spk_types.h +++ b/drivers/accessibility/speakup/spk_types.h @@ -53,7 +53,7 @@ enum var_id_t { }; typedef int (*special_func)(struct vc_data *vc, u_char type, u_char ch, - u_short key); + u16 key); #define COLOR_BUFFER_SIZE 160 diff --git a/drivers/accessibility/speakup/synth.c b/drivers/accessibility/speakup/synth.c index d8addbf3ad0d..d1ec1a7eb160 100644 --- a/drivers/accessibility/speakup/synth.c +++ b/drivers/accessibility/speakup/synth.c @@ -574,4 +574,4 @@ struct spk_synth *synth_current(void) } EXPORT_SYMBOL_GPL(synth_current); -short spk_punc_masks[] = { 0, SOME, MOST, PUNC, PUNC | B_SYM }; +u16 spk_punc_masks[] = { 0, SOME, MOST, PUNC, PUNC | B_SYM }; From 768ce60dc8fdea4ce37267d6a6d56193e9e7dc03 Mon Sep 17 00:00:00 2001 From: Xichao Zhao Date: Mon, 1 Jun 2026 01:07:57 +0200 Subject: [PATCH 031/135] accessibility: Use str_plural() to simplify the code Use the string choice helper function str_plural() to simplify the code. Signed-off-by: Xichao Zhao Signed-off-by: Samuel Thibault Reviewed-by: Samuel Thibault Link: https://patch.msgid.link/20260531230804.254962-9-samuel.thibault@ens-lyon.org Signed-off-by: Greg Kroah-Hartman --- drivers/accessibility/speakup/kobjects.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/accessibility/speakup/kobjects.c b/drivers/accessibility/speakup/kobjects.c index 9ff7a4c680db..a73b478e06de 100644 --- a/drivers/accessibility/speakup/kobjects.c +++ b/drivers/accessibility/speakup/kobjects.c @@ -98,7 +98,7 @@ static void report_char_chartab_status(int reset, int received, int used, if (rejected) snprintf(buf + (len - 1), sizeof(buf) - (len - 1), " with %d reject%s\n", - rejected, rejected > 1 ? "s" : ""); + rejected, str_plural(rejected)); pr_info("%s", buf); } } @@ -740,7 +740,7 @@ static void report_msg_status(int reset, int received, int used, if (rejected) snprintf(buf + (len - 1), sizeof(buf) - (len - 1), " with %d reject%s\n", - rejected, rejected > 1 ? "s" : ""); + rejected, str_plural(rejected)); pr_info("%s", buf); } } From 6a19ad4d68c95185308cd9e5d169b10a2cf236c8 Mon Sep 17 00:00:00 2001 From: Pavel Zhigulin Date: Mon, 1 Jun 2026 01:07:58 +0200 Subject: [PATCH 032/135] speakup: keyhelp: guard letter_offsets possible out-of-range indexing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit help_init() builds letter_offsets[] by using the first byte of each function name as an index via `(start & 31) - 1`. If function_names are overridden from sysfs (root) with a name starting outside [a–z], the index underflows or exceeds the array, leading to OOB write. Function names can be overridden with the following commands as root: modprobe speakup_soft echo "0 _bad" > /sys/accessibility/speakup/i18n/function_names # then press Insert+2 on /dev/tty This fix checks the first letter in help_init(), and if it is not in the [a–z] range the function returns an error to the caller. Eventually this error is propagated to drivers/accessibility/speakup/main.c:2217, which causes a bleep sound. Fixes: c6e3fd22cd53 ("Staging: add speakup to the staging directory") Signed-off-by: Pavel Zhigulin Signed-off-by: Samuel Thibault Link: https://patch.msgid.link/20260531230804.254962-10-samuel.thibault@ens-lyon.org Signed-off-by: Greg Kroah-Hartman --- drivers/accessibility/speakup/keyhelp.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/drivers/accessibility/speakup/keyhelp.c b/drivers/accessibility/speakup/keyhelp.c index 9c6e488adc2a..0940f430ac0f 100644 --- a/drivers/accessibility/speakup/keyhelp.c +++ b/drivers/accessibility/speakup/keyhelp.c @@ -8,6 +8,7 @@ */ #include +#include #include "spk_priv.h" #include "speakup.h" @@ -111,7 +112,7 @@ static void say_key(int key) spk_msg_get(MSG_KEYNAMES_START + (key - 1))); } -static int help_init(void) +static void help_init(void) { char start = SPACE; int i; @@ -120,13 +121,19 @@ static int help_init(void) state_tbl = spk_our_keys[0] + SHIFT_TBL_SIZE + 2; for (i = 0; i < num_funcs; i++) { char *cur_funcname = spk_msg_get(MSG_FUNCNAMES_START + i); + char first_letter; - if (start == *cur_funcname) + first_letter = tolower(*cur_funcname); + + /* Accept only 'a'..'z' to index letter_offsets[] safely */ + if (first_letter < 'a' || first_letter > 'z') continue; - start = *cur_funcname; + + if (start == first_letter) + continue; + start = first_letter; letter_offsets[(start & 31) - 1] = i; } - return 0; } int spk_handle_help(struct vc_data *vc, u_char type, u_char ch, u16 key) @@ -144,7 +151,7 @@ int spk_handle_help(struct vc_data *vc, u_char type, u_char ch, u16 key) synth_printf("%s\n", spk_msg_get(MSG_LEAVING_HELP)); return 1; } - ch |= 32; /* lower case */ + ch = tolower(ch); if (ch < 'a' || ch > 'z') return -1; if (letter_offsets[ch - 'a'] == -1) { From 7955022a98cef5dd475fe1b814650a79c5a3113f Mon Sep 17 00:00:00 2001 From: Bo Liu Date: Mon, 1 Jun 2026 01:07:59 +0200 Subject: [PATCH 033/135] Accessibility: speakup_soft: Fix double word in comments Remove the repeated word "the" in comments. Signed-off-by: Bo Liu Signed-off-by: Samuel Thibault Reviewed-by: Samuel Thibault Link: https://patch.msgid.link/20260531230804.254962-11-samuel.thibault@ens-lyon.org Signed-off-by: Greg Kroah-Hartman --- drivers/accessibility/speakup/speakup_soft.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/accessibility/speakup/speakup_soft.c b/drivers/accessibility/speakup/speakup_soft.c index 6d446824677b..6549bfb96e7f 100644 --- a/drivers/accessibility/speakup/speakup_soft.c +++ b/drivers/accessibility/speakup/speakup_soft.c @@ -446,7 +446,7 @@ static int softsynth_adjust(struct spk_synth *synth, struct st_var_header *var) if (var->var_id != PUNC_LEVEL) return 0; - /* We want to set the the speech synthesis punctuation level + /* We want to set the speech synthesis punctuation level * accordingly, so it properly tunes speaking A_PUNC characters */ var_data = var->data; if (!var_data) From 255418300df2c020d1a8848472b3481137a49342 Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Mon, 1 Jun 2026 01:08:00 +0200 Subject: [PATCH 034/135] speakup: Fix spelling of "re-enable" Detected using codespell. Signed-off-by: Bastien Nocera Signed-off-by: Samuel Thibault Link: https://patch.msgid.link/20260531230804.254962-12-samuel.thibault@ens-lyon.org Signed-off-by: Greg Kroah-Hartman --- drivers/accessibility/speakup/fakekey.c | 4 ++-- drivers/accessibility/speakup/synth.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/accessibility/speakup/fakekey.c b/drivers/accessibility/speakup/fakekey.c index 868c47b2a59b..9c0b2403876a 100644 --- a/drivers/accessibility/speakup/fakekey.c +++ b/drivers/accessibility/speakup/fakekey.c @@ -71,9 +71,9 @@ void speakup_fake_down_arrow(void) input_sync(virt_keyboard); __this_cpu_write(reporting_keystroke, false); - /* reenable preemption */ + /* re-enable preemption */ preempt_enable(); - /* reenable keyboard interrupts */ + /* re-enable keyboard interrupts */ local_irq_restore(flags); } diff --git a/drivers/accessibility/speakup/synth.c b/drivers/accessibility/speakup/synth.c index d1ec1a7eb160..11cba1fd8715 100644 --- a/drivers/accessibility/speakup/synth.c +++ b/drivers/accessibility/speakup/synth.c @@ -163,7 +163,7 @@ int spk_synth_is_alive_restart(struct spk_synth *synth) /* restart */ synth->alive = 1; synth_printf("%s", synth->init); - return 2; /* reenabled */ + return 2; /* re-enabled */ } pr_warn("%s: can't restart synth\n", synth->long_name); return 0; From 17035422e1f24d65c0b6b7d153e4f3e6fc59f410 Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Mon, 1 Jun 2026 01:08:01 +0200 Subject: [PATCH 035/135] speakup: Fix incorrect "index" plural It's indexes or indices. Given that the constant is called "STAT_index_valid", "indexes" was the preferred plural. Signed-off-by: Bastien Nocera Signed-off-by: Samuel Thibault Link: https://patch.msgid.link/20260531230804.254962-13-samuel.thibault@ens-lyon.org Signed-off-by: Greg Kroah-Hartman --- drivers/accessibility/speakup/speakup_decpc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/accessibility/speakup/speakup_decpc.c b/drivers/accessibility/speakup/speakup_decpc.c index 083ca9265805..1bf36d1e5477 100644 --- a/drivers/accessibility/speakup/speakup_decpc.c +++ b/drivers/accessibility/speakup/speakup_decpc.c @@ -41,7 +41,7 @@ #define STAT_new_index 0x0040 /* new last index ready */ #define STAT_new_status 0x0080 /* new status posted */ #define STAT_dma_state 0x0100 /* dma state toggle */ -#define STAT_index_valid 0x0200 /* indexs are valid */ +#define STAT_index_valid 0x0200 /* indexes are valid */ #define STAT_flushing 0x0400 /* flush in progress */ #define STAT_self_test 0x0800 /* module in self test */ #define MODE_ready 0xc000 /* module ready for next phase */ From 6a4c7d85f02df4b1def6ca3aa74668fa50848ed4 Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Mon, 1 Jun 2026 01:08:02 +0200 Subject: [PATCH 036/135] speakup: Fix typo in a speakup message s/read windo/read window/ Signed-off-by: Colin Ian King Signed-off-by: Bastien Nocera Signed-off-by: Samuel Thibault Reviewed-by: Samuel Thibault Link: https://patch.msgid.link/20260531230804.254962-14-samuel.thibault@ens-lyon.org Signed-off-by: Greg Kroah-Hartman --- drivers/accessibility/speakup/i18n.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/accessibility/speakup/i18n.c b/drivers/accessibility/speakup/i18n.c index d62079b1661f..554bf81f2c1c 100644 --- a/drivers/accessibility/speakup/i18n.c +++ b/drivers/accessibility/speakup/i18n.c @@ -31,7 +31,7 @@ static char *speakup_default_msgs[MSG_LAST_INDEX] = { [MSG_CURSORING_OFF] = "cursoring off", [MSG_CURSORING_ON] = "cursoring on", [MSG_HIGHLIGHT_TRACKING] = "highlight tracking", - [MSG_READ_WINDOW] = "read windo", + [MSG_READ_WINDOW] = "read window", [MSG_READ_ALL] = "read all", [MSG_EDIT_DONE] = "edit done", [MSG_WINDOW_ALREADY_SET] = "window already set, clear then reset", From a21164f89e09e84f50f8954ea4b0f8cc55af2d0b Mon Sep 17 00:00:00 2001 From: Francisco Maestre Date: Mon, 1 Jun 2026 01:08:03 +0200 Subject: [PATCH 037/135] speakup: speakup_soft: fix comment style and repeated word Fix comment style issues in speakup_soft.c: - Move the closing '*/' of the block comment to its own line, as required by the kernel coding style Signed-off-by: Francisco Maestre Signed-off-by: Samuel Thibault Link: https://patch.msgid.link/20260531230804.254962-15-samuel.thibault@ens-lyon.org Signed-off-by: Greg Kroah-Hartman --- drivers/accessibility/speakup/speakup_soft.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/accessibility/speakup/speakup_soft.c b/drivers/accessibility/speakup/speakup_soft.c index 6549bfb96e7f..57d83b82f1d1 100644 --- a/drivers/accessibility/speakup/speakup_soft.c +++ b/drivers/accessibility/speakup/speakup_soft.c @@ -447,7 +447,8 @@ static int softsynth_adjust(struct spk_synth *synth, struct st_var_header *var) return 0; /* We want to set the speech synthesis punctuation level - * accordingly, so it properly tunes speaking A_PUNC characters */ + * accordingly, so it properly tunes speaking A_PUNC characters + */ var_data = var->data; if (!var_data) return 0; From a76acbaec9b8fd74413646984d2e3626d0543e39 Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Mon, 1 Jun 2026 01:08:04 +0200 Subject: [PATCH 038/135] accessibility: speakup: unregister tty ldisc on later init failures The ldisc registration is intentionally non-fatal, since some synth drivers do not use tty/ldisc. However, once speakup_init() continues past the registration point and later fails, the init unwind path should mirror speakup_exit() and call spk_ttyio_unregister_ldisc(). Add the missing unregister call to the error path after synth_release(), matching the normal module exit cleanup order. Signed-off-by: Haoxiang Li Signed-off-by: Samuel Thibault Fixes: e23a9b439ce9 ("staging: speakup: safely register and unregister ldisc") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260531230804.254962-16-samuel.thibault@ens-lyon.org Signed-off-by: Greg Kroah-Hartman --- drivers/accessibility/speakup/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/accessibility/speakup/main.c b/drivers/accessibility/speakup/main.c index 0962741a2ca2..e9b7c2761f6f 100644 --- a/drivers/accessibility/speakup/main.c +++ b/drivers/accessibility/speakup/main.c @@ -2444,6 +2444,7 @@ error_kbdnotifier: mutex_lock(&spk_mutex); synth_release(); mutex_unlock(&spk_mutex); + spk_ttyio_unregister_ldisc(); speakup_kobj_exit(); error_kobjects: From 71375da8b473445f51f0668d5622922827402357 Mon Sep 17 00:00:00 2001 From: Shashwat Agrawal Date: Mon, 29 Jun 2026 19:24:04 +0530 Subject: [PATCH 039/135] comedi: ni_pcimio: set PCI-6220 dio_speed and ai_fifo_depth from NI specs The PCI-6220 board entry was missing .dio_speed, unlike the PXI-6220 and the other 622x boards in the table. Set it to 1000 ns to match those entries and the 1 MHz maximum DI/DO sample clock on Port 0 in the NI PCI/PXI-6220 specifications. Also update .ai_fifo_depth for PCI-6220 and PXI-6220 from 512 to 4095 samples, matching the documented AI input FIFO size and the rest of the 622x entries. Link: https://www.ni.com/docs/en-US/bundle/pci-pxi-6220-specs/page/specs.html Signed-off-by: Shashwat Agrawal Reviewed-by: Ian Abbott Link: https://patch.msgid.link/20260629135404.19835-1-shashwatagrawal473@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/drivers/ni_pcimio.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/comedi/drivers/ni_pcimio.c b/drivers/comedi/drivers/ni_pcimio.c index 4be9ca4f4828..299e2a0e9474 100644 --- a/drivers/comedi/drivers/ni_pcimio.c +++ b/drivers/comedi/drivers/ni_pcimio.c @@ -685,17 +685,18 @@ static const struct ni_board_struct ni_boards[] = { .name = "pci-6220", .n_adchan = 16, .ai_maxdata = 0xffff, - .ai_fifo_depth = 512, /* FIXME: guess */ + .ai_fifo_depth = 4095, .gainlkup = ai_gain_622x, .ai_speed = 4000, .reg_type = ni_reg_622x, .caldac = { caldac_none }, + .dio_speed = 1000, }, [BOARD_PXI6220] = { .name = "pxi-6220", .n_adchan = 16, .ai_maxdata = 0xffff, - .ai_fifo_depth = 512, /* FIXME: guess */ + .ai_fifo_depth = 4095, .gainlkup = ai_gain_622x, .ai_speed = 4000, .reg_type = ni_reg_622x, From 1ca44751915134f19e8627768159781e75b5da50 Mon Sep 17 00:00:00 2001 From: Ian Abbott Date: Thu, 18 Jun 2026 11:09:08 +0100 Subject: [PATCH 040/135] comedi: aio_iiro_16: Add sanity check to interrupt handler The driver requests an interrupt handler for the device, after setting device registers to disable interrupt generation. The interrupt handler should not be called prematurely unless the user-configured I/O port base address and/or IRQ number are incorrect or the hardware is bad. For safety, check the dev->attached flag in the interrupt handler to ensure the device has been fully set up, avoiding a possible null pointer dereference of dev->read_subdev. Reported-by: Jaeyoung Chung Link: https://lore.kernel.org/lkml/20260610115912.780131-1-jjy600901@snu.ac.kr/ Reported-by: Sangyun Kim Reported-by: Kyungwook Boo Signed-off-by: Ian Abbott Link: https://patch.msgid.link/20260618102949.26607-2-abbotti@mev.co.uk Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/drivers/aio_iiro_16.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/comedi/drivers/aio_iiro_16.c b/drivers/comedi/drivers/aio_iiro_16.c index d5d18fa2c638..52385b14a9a4 100644 --- a/drivers/comedi/drivers/aio_iiro_16.c +++ b/drivers/comedi/drivers/aio_iiro_16.c @@ -59,6 +59,9 @@ static irqreturn_t aio_iiro_16_cos(int irq, void *d) unsigned int status; unsigned int val; + if (!dev->attached) + return IRQ_NONE; + status = inb(dev->iobase + AIO_IIRO_16_STATUS); if (!(status & AIO_IIRO_16_STATUS_IRQE)) return IRQ_NONE; From e90d0550c5b3056c8821f5cd714f80902de0fca6 Mon Sep 17 00:00:00 2001 From: Ian Abbott Date: Thu, 18 Jun 2026 11:09:09 +0100 Subject: [PATCH 041/135] comedi: das6402: Add sanity check to interrupt handler The driver requests an interrupt handler for the device, after setting device registers to disable interrupt generation. The interrupt handler should not be called prematurely unless the user-configured I/O port base address and/or IRQ number are incorrect or the hardware is bad. For safety, check the dev->attached flag in the interrupt handler to ensure the device has been fully set up, avoiding a possible null pointer dereference of dev->read_subdev. Reported-by: Jaeyoung Chung Link: https://lore.kernel.org/lkml/20260610115912.780131-1-jjy600901@snu.ac.kr/ Reported-by: Sangyun Kim Reported-by: Kyungwook Boo Signed-off-by: Ian Abbott Link: https://patch.msgid.link/20260618102949.26607-3-abbotti@mev.co.uk Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/drivers/das6402.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/comedi/drivers/das6402.c b/drivers/comedi/drivers/das6402.c index 516a5d5a2840..384e9ba8d5df 100644 --- a/drivers/comedi/drivers/das6402.c +++ b/drivers/comedi/drivers/das6402.c @@ -173,10 +173,16 @@ static irqreturn_t das6402_interrupt(int irq, void *d) { struct comedi_device *dev = d; struct comedi_subdevice *s = dev->read_subdev; - struct comedi_async *async = s->async; - struct comedi_cmd *cmd = &async->cmd; + struct comedi_async *async; + struct comedi_cmd *cmd; unsigned int status; + if (!dev->attached) + return IRQ_NONE; + + async = s->async; + cmd = &async->cmd; + status = inb(dev->iobase + DAS6402_STATUS_REG); if ((status & DAS6402_STATUS_INT) == 0) return IRQ_NONE; From 8dda63825752ab19fcbc2a19ecab91d128112415 Mon Sep 17 00:00:00 2001 From: Ian Abbott Date: Thu, 18 Jun 2026 11:09:10 +0100 Subject: [PATCH 042/135] comedi: dt2811: Fix sanity check in interrupt handler The driver requests an interrupt handler for the device, after setting device registers to disable interrupt generation. The interrupt handler should not be called prematurely unless the user-configured I/O port base address and/or IRQ number are incorrect or the hardware is bad. For safety, the interrupt handler checks the dev->attached flag to ensure the device is fully set up, but it currently does that after dereferencing dev->read_subdev, which may be NULL if dev->attached is false. Move the check to avoid the possible null pointer dereference. Signed-off-by: Ian Abbott Link: https://patch.msgid.link/20260618102949.26607-4-abbotti@mev.co.uk Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/drivers/dt2811.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/comedi/drivers/dt2811.c b/drivers/comedi/drivers/dt2811.c index bcc4b5ef48e8..0438b8c90e44 100644 --- a/drivers/comedi/drivers/dt2811.c +++ b/drivers/comedi/drivers/dt2811.c @@ -193,13 +193,16 @@ static irqreturn_t dt2811_interrupt(int irq, void *d) { struct comedi_device *dev = d; struct comedi_subdevice *s = dev->read_subdev; - struct comedi_async *async = s->async; - struct comedi_cmd *cmd = &async->cmd; + struct comedi_async *async; + struct comedi_cmd *cmd; unsigned int status; if (!dev->attached) return IRQ_NONE; + async = s->async; + cmd = &async->cmd; + status = inb(dev->iobase + DT2811_ADCSR_REG); if (status & DT2811_ADCSR_ADERROR) { From e7356cdc7077a4f30fc5232a18e9fc6a4d404581 Mon Sep 17 00:00:00 2001 From: Ian Abbott Date: Thu, 18 Jun 2026 11:09:11 +0100 Subject: [PATCH 043/135] comedi: ni_at_a2150: Fix sanity check in interrupt handler The driver requests an interrupt handler for the device before it is fully set up. For safety, the interrupt handler checks the dev->attached flag to ensure the device is fully set up, but it currently does that after dereferencing various pointers which may be NULL if dev->attached is false. Move the check to avoid the possible null pointer dereferences. Signed-off-by: Ian Abbott Link: https://patch.msgid.link/20260618102949.26607-5-abbotti@mev.co.uk Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/drivers/ni_at_a2150.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/comedi/drivers/ni_at_a2150.c b/drivers/comedi/drivers/ni_at_a2150.c index 44221c928e32..86629b495fed 100644 --- a/drivers/comedi/drivers/ni_at_a2150.c +++ b/drivers/comedi/drivers/ni_at_a2150.c @@ -132,11 +132,11 @@ static irqreturn_t a2150_interrupt(int irq, void *d) struct comedi_device *dev = d; struct a2150_private *devpriv = dev->private; struct comedi_isadma *dma = devpriv->dma; - struct comedi_isadma_desc *desc = &dma->desc[0]; + struct comedi_isadma_desc *desc; struct comedi_subdevice *s = dev->read_subdev; - struct comedi_async *async = s->async; - struct comedi_cmd *cmd = &async->cmd; - unsigned short *buf = desc->virt_addr; + struct comedi_async *async; + struct comedi_cmd *cmd; + unsigned short *buf; unsigned int max_points, num_points, residue, leftover; unsigned short dpnt; int status; @@ -145,6 +145,11 @@ static irqreturn_t a2150_interrupt(int irq, void *d) if (!dev->attached) return IRQ_HANDLED; + desc = &dma->desc[0]; + async = s->async; + cmd = &async->cmd; + buf = desc->virt_addr; + status = inw(dev->iobase + STATUS_REG); if ((status & INTR_BIT) == 0) return IRQ_NONE; From 13f4796223489a5349b466ce54bb868f46c9fefd Mon Sep 17 00:00:00 2001 From: Ian Abbott Date: Thu, 18 Jun 2026 11:09:12 +0100 Subject: [PATCH 044/135] comedi: ni_atmio16d: Add sanity check to interrupt handler The driver requests an interrupt handler for the device, after setting device registers to disable interrupt generation. The interrupt handler should not be called prematurely unless the user-configured I/O port base address and/or IRQ number are incorrect or the hardware is bad. For safety, check the dev->attached flag in the interrupt handler to ensure the device has been fully set up, avoiding a possible null pointer dereference of dev->read_subdev. Reported-by: Jaeyoung Chung Link: https://lore.kernel.org/lkml/20260610115912.780131-1-jjy600901@snu.ac.kr/ Reported-by: Sangyun Kim Reported-by: Kyungwook Boo Signed-off-by: Ian Abbott Link: https://patch.msgid.link/20260618102949.26607-6-abbotti@mev.co.uk Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/drivers/ni_atmio16d.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/comedi/drivers/ni_atmio16d.c b/drivers/comedi/drivers/ni_atmio16d.c index b2772d909896..6765cdc276ca 100644 --- a/drivers/comedi/drivers/ni_atmio16d.c +++ b/drivers/comedi/drivers/ni_atmio16d.c @@ -223,6 +223,9 @@ static irqreturn_t atmio16d_interrupt(int irq, void *d) struct comedi_subdevice *s = dev->read_subdev; unsigned short val; + if (!dev->attached) + return IRQ_NONE; + val = inw(dev->iobase + AD_FIFO_REG); comedi_buf_write_samples(s, &val, 1); comedi_handle_events(dev, s); From f876cbfe3e066bc9e3f3210907bd534be82946c8 Mon Sep 17 00:00:00 2001 From: Ian Abbott Date: Thu, 18 Jun 2026 11:09:13 +0100 Subject: [PATCH 045/135] comedi: pcm711: Fix sanity check in interrupt handler The driver requests an interrupt handler for the device before it is fully set up. For safety, the interrupt handler checks the dev->attached flag to ensure the device is fully set up, but it currently does that after dereferencing the dev->read_dev pointer which may be NULL if dev->attached is false. Move the check to avoid the possible null pointer dereference. Reported-by: Jaeyoung Chung Link: https://lore.kernel.org/lkml/20260610115912.780131-1-jjy600901@snu.ac.kr/ Reported-by: Sangyun Kim Reported-by: Kyungwook Boo Signed-off-by: Ian Abbott Link: https://patch.msgid.link/20260618102949.26607-7-abbotti@mev.co.uk Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/drivers/pcl711.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/comedi/drivers/pcl711.c b/drivers/comedi/drivers/pcl711.c index 5d2c4b2aa3bb..8ad5789966f0 100644 --- a/drivers/comedi/drivers/pcl711.c +++ b/drivers/comedi/drivers/pcl711.c @@ -184,7 +184,7 @@ static irqreturn_t pcl711_interrupt(int irq, void *d) { struct comedi_device *dev = d; struct comedi_subdevice *s = dev->read_subdev; - struct comedi_cmd *cmd = &s->async->cmd; + struct comedi_cmd *cmd; unsigned short data; if (!dev->attached) { @@ -192,6 +192,7 @@ static irqreturn_t pcl711_interrupt(int irq, void *d) return IRQ_HANDLED; } + cmd = &s->async->cmd; data = pcl711_ai_get_sample(dev, s); outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG); From 4fdac5090ce3616283216d9a1d2b9664e8e20747 Mon Sep 17 00:00:00 2001 From: Ian Abbott Date: Thu, 18 Jun 2026 11:09:14 +0100 Subject: [PATCH 046/135] comedi: pcm816: Fix sanity check in interrupt handler The driver requests an interrupt handler for the device before it is fully set up. For safety, the interrupt handler checks the dev->attached flag to ensure the device is fully set up, but it currently does that after dereferencing the devpriv->dma pointer which may be NULL if dev->attached is false. Move the dereference of the devpriv->dma pointer after dev->attached has been checked to avoid the possible null pointer dereference. Signed-off-by: Ian Abbott Link: https://patch.msgid.link/20260618102949.26607-8-abbotti@mev.co.uk Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/drivers/pcl816.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/comedi/drivers/pcl816.c b/drivers/comedi/drivers/pcl816.c index 1fcb2f798c7a..b0d30cb1cfe9 100644 --- a/drivers/comedi/drivers/pcl816.c +++ b/drivers/comedi/drivers/pcl816.c @@ -242,7 +242,7 @@ static irqreturn_t pcl816_interrupt(int irq, void *d) struct comedi_subdevice *s = dev->read_subdev; struct pcl816_private *devpriv = dev->private; struct comedi_isadma *dma = devpriv->dma; - struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma]; + struct comedi_isadma_desc *desc; unsigned int nsamples; unsigned int bufptr; @@ -257,6 +257,7 @@ static irqreturn_t pcl816_interrupt(int irq, void *d) return IRQ_HANDLED; } + desc = &dma->desc[dma->cur_dma]; nsamples = comedi_bytes_to_samples(s, desc->size) - devpriv->ai_poll_ptr; bufptr = devpriv->ai_poll_ptr; From 956e8478261a575d1e87490f2e5db1788b8c209d Mon Sep 17 00:00:00 2001 From: Ian Abbott Date: Thu, 18 Jun 2026 11:09:15 +0100 Subject: [PATCH 047/135] comedi: pcm818: Fix sanity check in interrupt handler The driver requests an interrupt handler for the device before it is fully set up. For safety, the interrupt handler checks the dev->attached flag to ensure the device is fully set up, but it currently does that after dereferencing the dev->read_dev pointer which may be NULL if dev->attached is false. Move the check to avoid the possible null pointer dereference. Signed-off-by: Ian Abbott Link: https://patch.msgid.link/20260618102949.26607-9-abbotti@mev.co.uk Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/drivers/pcl818.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/comedi/drivers/pcl818.c b/drivers/comedi/drivers/pcl818.c index aa775a024fc7..89f32c493caa 100644 --- a/drivers/comedi/drivers/pcl818.c +++ b/drivers/comedi/drivers/pcl818.c @@ -534,13 +534,14 @@ static irqreturn_t pcl818_interrupt(int irq, void *d) struct comedi_device *dev = d; struct pcl818_private *devpriv = dev->private; struct comedi_subdevice *s = dev->read_subdev; - struct comedi_cmd *cmd = &s->async->cmd; + struct comedi_cmd *cmd; if (!dev->attached || !devpriv->ai_cmd_running) { pcl818_ai_clear_eoc(dev); return IRQ_HANDLED; } + cmd = &s->async->cmd; if (devpriv->ai_cmd_canceled) { /* * The cleanup from ai_cancel() has been delayed From ea72e2fc9026f375314421ddfa4dbdc49432a899 Mon Sep 17 00:00:00 2001 From: Ian Abbott Date: Thu, 18 Jun 2026 11:09:16 +0100 Subject: [PATCH 048/135] comedi: pcmmio: Add sanity check to interrupt handler The driver requests an interrupt handler for the device, after setting device registers to disable interrupt generation. The interrupt handler should not be called prematurely unless the user-configured I/O port base address and/or IRQ number are incorrect or the hardware is bad. For safety, check the dev->attached flag in the interrupt handler to ensure the device has been fully set up, avoiding a possible null pointer dereference of dev->read_subdev. Reported-by: Jaeyoung Chung Link: https://lore.kernel.org/lkml/20260610115912.780131-1-jjy600901@snu.ac.kr/ Reported-by: Sangyun Kim Reported-by: Kyungwook Boo Signed-off-by: Ian Abbott Link: https://patch.msgid.link/20260618102949.26607-10-abbotti@mev.co.uk Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/drivers/pcmmio.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/comedi/drivers/pcmmio.c b/drivers/comedi/drivers/pcmmio.c index d38202c8a12b..f42b7343b4e4 100644 --- a/drivers/comedi/drivers/pcmmio.c +++ b/drivers/comedi/drivers/pcmmio.c @@ -362,6 +362,9 @@ static irqreturn_t interrupt_pcmmio(int irq, void *d) unsigned int triggered; unsigned char int_pend; + if (!dev->attached) + return IRQ_NONE; + /* are there any interrupts pending */ int_pend = inb(dev->iobase + PCMMIO_INT_PENDING_REG) & 0x07; if (!int_pend) From 6f6f6644e011fa910db744e9978127a443b94001 Mon Sep 17 00:00:00 2001 From: Ian Abbott Date: Thu, 18 Jun 2026 11:09:17 +0100 Subject: [PATCH 049/135] comedi: pcmuio: Add sanity check to interrupt handler The driver requests an interrupt handler for the device, after setting device registers to disable interrupt generation. The interrupt handler should not be called prematurely unless the user-configured I/O port base address and/or IRQ number are incorrect or the hardware is bad. For safety, check the dev->attached flag in the interrupt handler pcmuio_interrupt() to ensure the device has been fully set up, avoiding a possible null pointer dereference of dev->subdevices by pcmuio_handle_asic_interrupt(). Also make use of the IRQ_HANDLED(x) macro for the normal return path of the interrupt handler. Signed-off-by: Ian Abbott Link: https://patch.msgid.link/20260618102949.26607-11-abbotti@mev.co.uk Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/drivers/pcmuio.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/comedi/drivers/pcmuio.c b/drivers/comedi/drivers/pcmuio.c index 0995911a3ea3..d9995cbeecb6 100644 --- a/drivers/comedi/drivers/pcmuio.c +++ b/drivers/comedi/drivers/pcmuio.c @@ -362,12 +362,15 @@ static irqreturn_t pcmuio_interrupt(int irq, void *d) struct pcmuio_private *devpriv = dev->private; int handled = 0; + if (!dev->attached) + return IRQ_NONE; + if (irq == dev->irq) handled += pcmuio_handle_asic_interrupt(dev, 0); if (irq == devpriv->irq2) handled += pcmuio_handle_asic_interrupt(dev, 1); - return handled ? IRQ_HANDLED : IRQ_NONE; + return IRQ_RETVAL(handled); } /* chip->spinlock is already locked */ From 1bc9538df6be8dac8727179d8bcc0d880648361b Mon Sep 17 00:00:00 2001 From: Ian Abbott Date: Thu, 18 Jun 2026 11:09:18 +0100 Subject: [PATCH 050/135] comedi: quatech_daqp_cs: Fix sanity check in interrupt handler The driver requests an interrupt handler for the device before it is fully set up. For safety, the interrupt handler checks the dev->attached flag to ensure the device is fully set up, but it currently does that after dereferencing the dev->read_dev pointer which may be NULL if dev->attached is false. Move the check to avoid the possible null pointer dereference. Signed-off-by: Ian Abbott Link: https://patch.msgid.link/20260618102949.26607-12-abbotti@mev.co.uk Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/drivers/quatech_daqp_cs.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/comedi/drivers/quatech_daqp_cs.c b/drivers/comedi/drivers/quatech_daqp_cs.c index 2a76c75c513b..d5e3e213e233 100644 --- a/drivers/comedi/drivers/quatech_daqp_cs.c +++ b/drivers/comedi/drivers/quatech_daqp_cs.c @@ -211,13 +211,15 @@ static irqreturn_t daqp_interrupt(int irq, void *dev_id) { struct comedi_device *dev = dev_id; struct comedi_subdevice *s = dev->read_subdev; - struct comedi_cmd *cmd = &s->async->cmd; + struct comedi_cmd *cmd; int loop_limit = 10000; int status; if (!dev->attached) return IRQ_NONE; + cmd = &s->async->cmd; + status = inb(dev->iobase + DAQP_STATUS_REG); if (!(status & DAQP_STATUS_EVENTS)) return IRQ_NONE; From 1123dc1ae35f8600ef191930b3c4635b62300cb2 Mon Sep 17 00:00:00 2001 From: Pankaj Patil Date: Sat, 30 May 2026 21:53:20 +0100 Subject: [PATCH 051/135] dt-bindings: nvmem: qfprom: Add glymur compatible Document compatible string for the QFPROM on Glymur platform. Signed-off-by: Pankaj Patil Reviewed-by: Krzysztof Kozlowski Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530205333.117458-2-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml b/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml index 2ab047f2bb69..aad8f5ea6fff 100644 --- a/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml +++ b/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml @@ -19,6 +19,7 @@ properties: - enum: - qcom,apq8064-qfprom - qcom,apq8084-qfprom + - qcom,glymur-qfprom - qcom,ipq5018-qfprom - qcom,ipq5332-qfprom - qcom,ipq5424-qfprom From 9f99bb979f930e52e6861114fe03535d4bc2f374 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sat, 30 May 2026 21:53:21 +0100 Subject: [PATCH 052/135] nvmem: rockchip-otp: alloc clks with main struct Use a flexible array member to simplify allocation slightly. No need for a separate calloc. Signed-off-by: Rosen Penev Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530205333.117458-3-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/rockchip-otp.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/nvmem/rockchip-otp.c b/drivers/nvmem/rockchip-otp.c index 0ec78b5e19e7..2c0feb036f3f 100644 --- a/drivers/nvmem/rockchip-otp.c +++ b/drivers/nvmem/rockchip-otp.c @@ -78,9 +78,9 @@ struct rockchip_data { struct rockchip_otp { struct device *dev; void __iomem *base; - struct clk_bulk_data *clks; struct reset_control *rst; const struct rockchip_data *data; + struct clk_bulk_data clks[]; }; static int rockchip_otp_reset(struct rockchip_otp *otp) @@ -424,7 +424,7 @@ static int rockchip_otp_probe(struct platform_device *pdev) if (!data) return dev_err_probe(dev, -EINVAL, "failed to get match data\n"); - otp = devm_kzalloc(&pdev->dev, sizeof(struct rockchip_otp), + otp = devm_kzalloc(&pdev->dev, struct_size(otp, clks, data->num_clks), GFP_KERNEL); if (!otp) return -ENOMEM; @@ -436,11 +436,6 @@ static int rockchip_otp_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(otp->base), "failed to ioremap resource\n"); - otp->clks = devm_kcalloc(dev, data->num_clks, sizeof(*otp->clks), - GFP_KERNEL); - if (!otp->clks) - return -ENOMEM; - for (i = 0; i < data->num_clks; ++i) otp->clks[i].id = data->clks[i]; From bd66bfb0bf20919b07681a589bb2aa3f22b191b9 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Sat, 30 May 2026 21:53:22 +0100 Subject: [PATCH 053/135] nvmem: core: Default to read-only if wp-gpios present In case the nvmem DT node contains "wp-gpios" DT property, the device currently defaults to read-write and the force_ro sysfs attribute reads 0. Switch to the default read-only, which is both safer, and aligned with eMMC HW BOOT partition force_ro sysfs attribute behavior, which also defaults to read-only. The adjustment of nvmem->read_only value to read-only in case wp-gpios DT property is present must be done only after the device_add() got called because device_add() does internally call nvmem_bin_attr_get_umode(), which configures the permissions of 'nvmem' bin attr based on the value of nvmem->read_only that is only parsed from DT property 'read-only', without any adjustment. This way, if DT property 'read-only' is present, the 'nvmem' attribute is always read-only. Otherwise, if the device is writeable, then 'nvmem' attribute is writeable, and nvmem->read_only defaults to read-only, but can be switched to read-write at runtime via the 'force_ro' attribute. The updated behavior can be tested as follows: Current content: " $ cat /sys/bus/nvmem/devices/logging7/force_ro 1 $ hexdump -C /sys/bus/nvmem/devices/logging7/nvmem 00000000 66 6f 6f 0a ff ff ff ff " Write into default-read-only device: " $ echo bar > /sys/bus/nvmem/devices/logging7/nvmem bash: echo: write error: Operation not permitted $ cat /sys/bus/nvmem/devices/logging7/force_ro 1 " Unlock and write into device: " $ echo 0 > /sys/bus/nvmem/devices/logging7/force_ro $ cat /sys/bus/nvmem/devices/logging7/force_ro 0 $ echo bar > /sys/bus/nvmem/devices/logging7/nvmem $ hexdump -C /sys/bus/nvmem/devices/logging7/nvmem 00000000 62 61 72 0a ff ff ff ff " Relock and write into device, fails because device is read-only again: " $ echo 1 > /sys/bus/nvmem/devices/logging7/force_ro $ echo baz > /sys/bus/nvmem/devices/logging7/nvmem bash: echo: write error: Operation not permitted $ hexdump -C /sys/bus/nvmem/devices/logging7/nvmem 00000000 62 61 72 0a ff ff ff ff " Reviewed-by: Bartosz Golaszewski Signed-off-by: Marek Vasut Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530205333.117458-4-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index e871181751f3..6bcb90760bee 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -1019,6 +1019,10 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) if (rval) goto err_remove_dev; + /* If the device has WP GPIO, default to read-only */ + if (nvmem->wp_gpio) + nvmem->read_only = true; + #ifdef CONFIG_NVMEM_SYSFS rval = nvmem_populate_sysfs_cells(nvmem); if (rval) From ec4b806d0c7e688337241e9aa4b153082fd3275b Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Sat, 30 May 2026 21:53:23 +0100 Subject: [PATCH 054/135] dt-bindings: nvmem: qfprom: qcom: Add Hawi compatible Document compatible string for the QFPROM on Hawi platform. Signed-off-by: Mukesh Ojha Acked-by: Krzysztof Kozlowski Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530205333.117458-5-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml b/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml index aad8f5ea6fff..721c34388746 100644 --- a/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml +++ b/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml @@ -20,6 +20,7 @@ properties: - qcom,apq8064-qfprom - qcom,apq8084-qfprom - qcom,glymur-qfprom + - qcom,hawi-qfprom - qcom,ipq5018-qfprom - qcom,ipq5332-qfprom - qcom,ipq5424-qfprom From 18036ec7334ba6440eb774cdd7e94db09c581fbd Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sat, 30 May 2026 21:53:24 +0100 Subject: [PATCH 055/135] nvmem: nintendo-otp: Use of_device_get_match_data() Use of_device_get_match_data() to retrieve the devtype data instead of open-coding the OF match lookup and dereferencing match->data. This also replaces the deprecated of_device.h include with of.h. Assisted-by: Codex:GPT-5.5 Signed-off-by: Rosen Penev Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530205333.117458-6-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/nintendo-otp.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/nvmem/nintendo-otp.c b/drivers/nvmem/nintendo-otp.c index 4440d4e5fb83..a4d986e4588e 100644 --- a/drivers/nvmem/nintendo-otp.c +++ b/drivers/nvmem/nintendo-otp.c @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include #define HW_OTPCMD 0 @@ -73,8 +73,7 @@ MODULE_DEVICE_TABLE(of, nintendo_otp_of_table); static int nintendo_otp_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - const struct of_device_id *of_id = - of_match_device(nintendo_otp_of_table, dev); + const struct nintendo_otp_devtype_data *data; struct nvmem_device *nvmem; struct nintendo_otp_priv *priv; @@ -94,8 +93,8 @@ static int nintendo_otp_probe(struct platform_device *pdev) if (IS_ERR(priv->regs)) return PTR_ERR(priv->regs); - if (of_id->data) { - const struct nintendo_otp_devtype_data *data = of_id->data; + data = of_device_get_match_data(dev); + if (data) { config.name = data->name; config.size = data->num_banks * BANK_SIZE; } From ef558843eff499590e5123c5478140c37dadee2f Mon Sep 17 00:00:00 2001 From: Alexander Koskovich Date: Sat, 30 May 2026 21:53:25 +0100 Subject: [PATCH 056/135] dt-bindings: nvmem: qfprom: Add Milos compatible Document compatible string for the QFPROM on Milos platform. Signed-off-by: Alexander Koskovich Reviewed-by: Bjorn Andersson Reviewed-by: Krzysztof Kozlowski Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530205333.117458-7-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml b/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml index 721c34388746..646a0da7e839 100644 --- a/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml +++ b/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml @@ -29,6 +29,7 @@ properties: - qcom,ipq8074-qfprom - qcom,ipq9574-qfprom - qcom,kaanapali-qfprom + - qcom,milos-qfprom - qcom,msm8226-qfprom - qcom,msm8916-qfprom - qcom,msm8917-qfprom From 7acd1e983c9c3b8f5749bea497208d5656e23dee Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Sat, 30 May 2026 21:53:26 +0100 Subject: [PATCH 057/135] dt-bindings: nvmem: lan9662-otpc: Add LAN969x series Unlike LAN966x series which has 8K of OTP space, LAN969x series has 16K of OTP space, so document the compatible. Acked-by: Conor Dooley Signed-off-by: Robert Marko Reviewed-by: Claudiu Beznea Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530205333.117458-8-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/nvmem/microchip,lan9662-otpc.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/nvmem/microchip,lan9662-otpc.yaml b/Documentation/devicetree/bindings/nvmem/microchip,lan9662-otpc.yaml index f97c6beb4766..c03e96afe564 100644 --- a/Documentation/devicetree/bindings/nvmem/microchip,lan9662-otpc.yaml +++ b/Documentation/devicetree/bindings/nvmem/microchip,lan9662-otpc.yaml @@ -25,6 +25,7 @@ properties: - const: microchip,lan9662-otpc - enum: - microchip,lan9662-otpc + - microchip,lan9691-otpc reg: maxItems: 1 From 4d8d405b139c7c2fd59cd4d1cc15bea0120c4dc7 Mon Sep 17 00:00:00 2001 From: Horatiu Vultur Date: Sat, 30 May 2026 21:53:27 +0100 Subject: [PATCH 058/135] nvmem: lan9662-otp: add support for LAN969x Microchip LAN969x provides OTP with the same control logic, only the size differs as LAN969x has 16KB of OTP instead of 8KB like on LAN966x. Signed-off-by: Horatiu Vultur Signed-off-by: Robert Marko Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530205333.117458-9-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/Kconfig | 2 +- drivers/nvmem/lan9662-otpc.c | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index 74ddbd0f79b0..78b648e14727 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -138,7 +138,7 @@ config NVMEM_JZ4780_EFUSE config NVMEM_LAN9662_OTPC tristate "Microchip LAN9662 OTP controller support" - depends on SOC_LAN966 || COMPILE_TEST + depends on SOC_LAN966 || ARCH_LAN969X || COMPILE_TEST depends on HAS_IOMEM help This driver enables the OTP controller available on Microchip LAN9662 diff --git a/drivers/nvmem/lan9662-otpc.c b/drivers/nvmem/lan9662-otpc.c index 56fc19f092a7..62d1d6381bf8 100644 --- a/drivers/nvmem/lan9662-otpc.c +++ b/drivers/nvmem/lan9662-otpc.c @@ -27,7 +27,6 @@ #define OTP_OTP_STATUS_OTP_CPUMPEN BIT(1) #define OTP_OTP_STATUS_OTP_BUSY BIT(0) -#define OTP_MEM_SIZE 8192 #define OTP_SLEEP_US 10 #define OTP_TIMEOUT_US 500000 @@ -176,7 +175,6 @@ static struct nvmem_config otp_config = { .word_size = 1, .reg_read = lan9662_otp_read, .reg_write = lan9662_otp_write, - .size = OTP_MEM_SIZE, }; static int lan9662_otp_probe(struct platform_device *pdev) @@ -196,6 +194,7 @@ static int lan9662_otp_probe(struct platform_device *pdev) otp_config.priv = otp; otp_config.dev = dev; + otp_config.size = (uintptr_t) device_get_match_data(dev); nvmem = devm_nvmem_register(dev, &otp_config); @@ -203,7 +202,14 @@ static int lan9662_otp_probe(struct platform_device *pdev) } static const struct of_device_id lan9662_otp_match[] = { - { .compatible = "microchip,lan9662-otpc", }, + { + .compatible = "microchip,lan9662-otpc", + .data = (const void *) SZ_8K, + }, + { + .compatible = "microchip,lan9691-otpc", + .data = (const void *) SZ_16K, + }, { }, }; MODULE_DEVICE_TABLE(of, lan9662_otp_match); From 31a75f07b9e90f46087a19b7eea3e6f96055a34b Mon Sep 17 00:00:00 2001 From: Komal Bajaj Date: Sat, 30 May 2026 21:53:28 +0100 Subject: [PATCH 059/135] dt-bindings: nvmem: qcom,qfprom: Add Shikra compatible Document compatible string for the QFPROM on Qualcomm Shikra SoC. Signed-off-by: Komal Bajaj Acked-by: Rob Herring (Arm) Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530205333.117458-10-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml b/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml index 646a0da7e839..8134ddb54e13 100644 --- a/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml +++ b/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml @@ -51,6 +51,7 @@ properties: - qcom,sdm630-qfprom - qcom,sdm670-qfprom - qcom,sdm845-qfprom + - qcom,shikra-qfprom - qcom,sm6115-qfprom - qcom,sm6350-qfprom - qcom,sm6375-qfprom From 2956111189fb240a5f2ea1816c7ce97d219b558c Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Sat, 30 May 2026 21:53:29 +0100 Subject: [PATCH 060/135] dt-bindings: nvmem: airoha: add SMC eFuses schema Add Airoha SMC eFuses schema to document new Airoha SoC AN7581/AN7583 way of accessing the 2 eFuse bank via the SMC command. Each eFuse bank expose 64 eFuse cells of 32 bit used to give information on HW Revision, PHY Calibration, Device Model, Private Key and all kind of other info specific to the SoC or the running system. Signed-off-by: Christian Marangi Reviewed-by: Rob Herring (Arm) Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530205333.117458-11-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../bindings/nvmem/airoha,smc-efuses.yaml | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 Documentation/devicetree/bindings/nvmem/airoha,smc-efuses.yaml diff --git a/Documentation/devicetree/bindings/nvmem/airoha,smc-efuses.yaml b/Documentation/devicetree/bindings/nvmem/airoha,smc-efuses.yaml new file mode 100644 index 000000000000..c52f8d4bec39 --- /dev/null +++ b/Documentation/devicetree/bindings/nvmem/airoha,smc-efuses.yaml @@ -0,0 +1,67 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/nvmem/airoha,smc-efuses.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Airoha SMC eFuses + +description: | + Airoha new SoC AN7581 expose banks of eFuse accessible + via specific SMC commands. + + 2 different bank of eFuse or 64 cells of 32 bit are exposed + read-only used to give information on HW Revision, PHY Calibration, + Device Model, Private Key... + +maintainers: + - Christian Marangi + +properties: + compatible: + enum: + - airoha,an7581-efuses + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + +patternProperties: + '^efuse-bank@[0-1]$': + type: object + + allOf: + - $ref: nvmem.yaml# + + properties: + reg: + description: Identify the eFuse bank. + enum: [0, 1] + + required: + - reg + + unevaluatedProperties: false + +required: + - compatible + - '#address-cells' + - '#size-cells' + +additionalProperties: false + +examples: + - | + efuse { + compatible = "airoha,an7581-efuses"; + #address-cells = <1>; + #size-cells = <0>; + + efuse-bank@0 { + reg = <0>; + }; + }; + +... From b7846af2e6ca87fecac0e90c15e69217e174b126 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Sat, 30 May 2026 21:53:30 +0100 Subject: [PATCH 061/135] nvmem: airoha: Add support for SMC eFUSE Add support for SMC eFUSE on AN7581 SoC. The SoC have 2 set of 2048 bits of eFUSE that are used to read calibration value for PCIe, Thermal, USB and other specific info of the SoC like revision and HW device present. eFuse value are taken by sending SMC command. ATF is responsible of validaing the data and rejecting reading protected data (like Private Key). In such case the SMC command will return non-zero value on a0 register. Signed-off-by: Christian Marangi Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530205333.117458-12-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/Kconfig | 13 ++++ drivers/nvmem/Makefile | 2 + drivers/nvmem/airoha-smc-efuses.c | 125 ++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+) create mode 100644 drivers/nvmem/airoha-smc-efuses.c diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index 78b648e14727..77ff62d1cd01 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -28,6 +28,19 @@ source "drivers/nvmem/layouts/Kconfig" # Devices +config NVMEM_AIROHA_SMC_EFUSES + tristate "Airoha SMC eFuse support" + depends on ARCH_AIROHA || COMPILE_TEST + depends on HAVE_ARM_SMCCC + default ARCH_AIROHA + help + Say y here to enable support for reading eFuses on Airoha AN7581 + SoCs. These are e.g. used to store factory programmed + calibration data required for the PCIe or the USB-C PHY or Thermal. + + This driver can also be built as a module. If so, the module will + be called nvmem-airoha-smc-efuses. + config NVMEM_AN8855_EFUSE tristate "Airoha AN8855 eFuse support" depends on COMPILE_TEST diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile index 7252b8ec88d4..f6f2bc51dee1 100644 --- a/drivers/nvmem/Makefile +++ b/drivers/nvmem/Makefile @@ -10,6 +10,8 @@ nvmem_layouts-y := layouts.o obj-y += layouts/ # Devices +obj-$(CONFIG_NVMEM_AIROHA_SMC_EFUSES) += nvmem-airoha-smc-efuses.o +nvmem-airoha-smc-efuses-y := airoha-smc-efuses.o obj-$(CONFIG_NVMEM_AN8855_EFUSE) += nvmem-an8855-efuse.o nvmem-an8855-efuse-y := an8855-efuse.o obj-$(CONFIG_NVMEM_APPLE_EFUSES) += nvmem-apple-efuses.o diff --git a/drivers/nvmem/airoha-smc-efuses.c b/drivers/nvmem/airoha-smc-efuses.c new file mode 100644 index 000000000000..e56a99f4aa1f --- /dev/null +++ b/drivers/nvmem/airoha-smc-efuses.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Author: Christian Marangi + */ + +#include +#include +#include +#include +#include +#include +#include + +#define AIROHA_SMC_EFUSE_FID 0x82000001 +#define AIROHA_SMC_EFUSE_SUB_ID_READ 0x44414552 + +#define AIROHA_EFUSE_CELLS 64 + +struct airoha_efuse_bank_priv { + u32 bank_index; +}; + +static int airoha_efuse_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct regmap *regmap = context; + + return regmap_bulk_read(regmap, offset, + val, bytes / sizeof(u32)); +} + +static int airoha_efuse_reg_read(void *context, unsigned int offset, + unsigned int *val) +{ + struct airoha_efuse_bank_priv *priv = context; + struct arm_smccc_res res; + + arm_smccc_1_1_invoke(AIROHA_SMC_EFUSE_FID, + AIROHA_SMC_EFUSE_SUB_ID_READ, + priv->bank_index, offset, 0, 0, 0, 0, &res); + + /* check if SMC reported an error */ + if (res.a0) + return -EIO; + + *val = res.a1; + return 0; +} + +static int airoha_efuse_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int ret; + + for_each_child_of_node_scoped(dev->of_node, child) { + struct nvmem_config nvmem_config = { + .size = AIROHA_EFUSE_CELLS * sizeof(u32), + .stride = sizeof(u32), + .word_size = sizeof(u32), + .reg_read = airoha_efuse_read, + }; + struct regmap_config regmap_config = { + .reg_read = airoha_efuse_reg_read, + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + }; + struct airoha_efuse_bank_priv *priv; + struct nvmem_device *nvmem; + struct regmap *regmap; + const char *name; + u32 bank; + + ret = of_property_read_u32(child, "reg", &bank); + if (ret) + return ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + name = devm_kasprintf(dev, GFP_KERNEL, "airoha-efuse-%u", + bank); + if (!name) + return -ENOMEM; + + priv->bank_index = bank; + + regmap_config.name = name; + regmap = devm_regmap_init(dev, NULL, priv, + ®map_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + nvmem_config.name = name; + nvmem_config.priv = regmap; + nvmem_config.dev = dev; + nvmem_config.id = bank; + nvmem_config.of_node = child; + nvmem = devm_nvmem_register(dev, &nvmem_config); + if (IS_ERR(nvmem)) + return PTR_ERR(nvmem); + } + + return 0; +} + +static const struct of_device_id airoha_efuse_of_match[] = { + { .compatible = "airoha,an7581-efuses", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, airoha_efuse_of_match); + +static struct platform_driver airoha_efuse_driver = { + .probe = airoha_efuse_probe, + .driver = { + .name = "airoha-efuse", + .of_match_table = airoha_efuse_of_match, + }, +}; +module_platform_driver(airoha_efuse_driver); + +MODULE_AUTHOR("Christian Marangi "); +MODULE_DESCRIPTION("Driver for Airoha SMC eFUSEs"); +MODULE_LICENSE("GPL"); From 302fbf6e36aa465f49b7733fc29e280d8ebeb7a6 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sat, 30 May 2026 21:53:31 +0100 Subject: [PATCH 062/135] nvmem: qcom: Unify user-visible "Qualcomm" name Various names for Qualcomm as a company are used in user-visible config options: QCOM, Qualcomm and Qualcomm Technologies. Switch to unified "Qualcomm" so it will be easier for users to identify the options when for example running menuconfig. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530205333.117458-13-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index 77ff62d1cd01..730d71642214 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -275,7 +275,7 @@ config NVMEM_S32G_OCOTP Programmable memory pages. config NVMEM_QCOM_QFPROM - tristate "QCOM QFPROM Support" + tristate "Qualcomm QFPROM Support" depends on ARCH_QCOM || COMPILE_TEST depends on HAS_IOMEM help @@ -286,7 +286,7 @@ config NVMEM_QCOM_QFPROM will be called nvmem_qfprom. config NVMEM_QCOM_SEC_QFPROM - tristate "QCOM SECURE QFPROM Support" + tristate "Qualcomm SECURE QFPROM Support" depends on ARCH_QCOM || COMPILE_TEST depends on HAS_IOMEM depends on OF From 804a588eb58a29c74c4c2f0deea204870929b599 Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Sat, 30 May 2026 21:53:32 +0100 Subject: [PATCH 063/135] nvmem: cleanup dead code in Kconfig There is already an 'if NVMEM' condition wrapping NVMEM_RCAR_EFUSE, making the 'depends on' statement a duplicate dependency (dead code). I propose leaving the outer 'if NVMEM...endif' and removing the individual 'depends on' statement. This dead code was found by kconfirm, a static analysis tool for Kconfig. Signed-off-by: Julian Braha Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530205333.117458-14-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/Kconfig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index 730d71642214..e10f7ff725ff 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -316,7 +316,6 @@ config NVMEM_RAVE_SP_EEPROM config NVMEM_RCAR_EFUSE tristate "Renesas R-Car Gen4 E-FUSE support" depends on (ARCH_RENESAS && ARM64) || COMPILE_TEST - depends on NVMEM help Enable support for reading the fuses in the E-FUSE or OTP non-volatile memory block on Renesas R-Car Gen4 SoCs. @@ -496,4 +495,4 @@ config NVMEM_QORIQ_EFUSE This driver can also be built as a module. If so, the module will be called nvmem_qoriq_efuse. -endif +endif # NVMEM From 45cb0223740b863089ce3cc523155cf7a55e1479 Mon Sep 17 00:00:00 2001 From: Tomasz Maciej Nowak Date: Sat, 30 May 2026 21:53:33 +0100 Subject: [PATCH 064/135] nvmem: layouts: u-boot-env: check earlier for ethaddr length Unfortunately the ethaddr value in U-Boot environment might be enclosed in single/double quotes or be something completely different. This can make it different than MAC_ADDR_STR_LEN, which results in EINVAL returned by ethaddr post process. Move the check for length earlier, to skip post processing, so nvmem could still present ethaddr value as a string if the value doesn't match MAC_ADDR_STR_LEN. Signed-off-by: Tomasz Maciej Nowak Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530205333.117458-15-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/layouts/u-boot-env.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/nvmem/layouts/u-boot-env.c b/drivers/nvmem/layouts/u-boot-env.c index f27f387bb52a..33ec2350386f 100644 --- a/drivers/nvmem/layouts/u-boot-env.c +++ b/drivers/nvmem/layouts/u-boot-env.c @@ -38,9 +38,6 @@ static int u_boot_env_read_post_process_ethaddr(void *context, const char *id, i { u8 mac[ETH_ALEN]; - if (bytes != MAC_ADDR_STR_LEN) - return -EINVAL; - if (!mac_pton(buf, mac)) return -EINVAL; @@ -75,7 +72,7 @@ static int u_boot_env_parse_cells(struct device *dev, struct nvmem_device *nvmem info.offset = data_offset + value - data; info.bytes = strlen(value); info.np = of_get_child_by_name(dev->of_node, info.name); - if (!strcmp(var, "ethaddr")) { + if (!strcmp(var, "ethaddr") && info.bytes == MAC_ADDR_STR_LEN) { info.raw_len = strlen(value); info.bytes = ETH_ALEN; info.read_post_process = u_boot_env_read_post_process_ethaddr; From 0c419df9c190b80136cc774325f84238e430e905 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Thu, 9 Jul 2026 13:47:22 +0300 Subject: [PATCH 065/135] mei: lb: fix incorrect type in assignment Fix the mix between __le32 and integer by casting the MEI_LB2_CMD constant as __le32 while using it. Fixes sparse waring: drivers/misc/mei/mei_lb.c:284:32: sparse: sparse: restricted __le32 degrades to integer drivers/misc/mei/mei_lb.c:330:40: sparse: sparse: incorrect type in assignment (different base types) @@ expected restricted __le32 [usertype] command_id @@ got int @@ drivers/misc/mei/mei_lb.c:330:40: sparse: expected restricted __le32 [usertype] command_id drivers/misc/mei/mei_lb.c:330:40: sparse: got int Fixes: 773a43b8627f ("mei: lb: add late binding version 2") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202605091533.79Zcv3CX-lkp@intel.com/ Signed-off-by: Alexander Usyskin Link: https://patch.msgid.link/20260709-fix_type_le-v3-1-478761151e05@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/mei_lb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/misc/mei/mei_lb.c b/drivers/misc/mei/mei_lb.c index f6a258c2b838..9fa69acf28d5 100644 --- a/drivers/misc/mei/mei_lb.c +++ b/drivers/misc/mei/mei_lb.c @@ -281,7 +281,7 @@ static int mei_lb_check_response_v2(const struct device *dev, ssize_t bytes, bytes, sizeof(rsp->rheader)); return -ENOMSG; } - if (rsp->rheader.header.command_id != MEI_LB2_CMD) { + if (rsp->rheader.header.command_id != cpu_to_le32(MEI_LB2_CMD)) { dev_err(dev, "Mismatch command: 0x%x instead of 0x%x\n", rsp->rheader.header.command_id, MEI_LB2_CMD); return -EPROTO; @@ -327,7 +327,7 @@ static int mei_lb_push_payload_v2(struct device *dev, struct mei_cl_device *clde if (sent_data + chunk_size == payload_size) last_chunk = MEI_LB2_FLAG_LST_CHUNK; - req->header.command_id = MEI_LB2_CMD; + req->header.command_id = cpu_to_le32(MEI_LB2_CMD); req->type = cpu_to_le32(type); req->flags = cpu_to_le32(flags | first_chunk | last_chunk); req->reserved = 0; From 0502b95447e89c0ce1bff198e429fcdacb93d389 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Tue, 9 Jun 2026 16:55:13 +0200 Subject: [PATCH 066/135] comedi: Drop unused assignments from pnp_device_id arrays MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Explicitly assigning .driver_data in drivers that don't use this member is silly and a bit irritating. Drop these. Also simplify the list terminator entry to be just empty to match what most other device_id tables do. There is no changed semantic, not even a change in the compiled result. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/c938b407cc16e9db2a59c67f390f073eeee6f1b3.1781016848.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/drivers/c6xdigio.c | 6 +++--- drivers/comedi/drivers/ni_atmio.c | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/comedi/drivers/c6xdigio.c b/drivers/comedi/drivers/c6xdigio.c index b6563a48ada6..fd36f820979c 100644 --- a/drivers/comedi/drivers/c6xdigio.c +++ b/drivers/comedi/drivers/c6xdigio.c @@ -224,10 +224,10 @@ static void c6xdigio_init(struct comedi_device *dev) static const struct pnp_device_id c6xdigio_pnp_tbl[] = { /* Standard LPT Printer Port */ - {.id = "PNP0400", .driver_data = 0}, + { .id = "PNP0400" }, /* ECP Printer Port */ - {.id = "PNP0401", .driver_data = 0}, - {} + { .id = "PNP0401" }, + { } }; static struct pnp_driver c6xdigio_pnp_driver = { diff --git a/drivers/comedi/drivers/ni_atmio.c b/drivers/comedi/drivers/ni_atmio.c index 7bc336333ace..537301eee9bf 100644 --- a/drivers/comedi/drivers/ni_atmio.c +++ b/drivers/comedi/drivers/ni_atmio.c @@ -215,13 +215,13 @@ static const int ni_irqpin[] = { #include "ni_mio_common.c" -static const struct pnp_device_id __maybe_unused device_ids[] = { - {.id = "NIC1900", .driver_data = 0}, - {.id = "NIC2400", .driver_data = 0}, - {.id = "NIC2500", .driver_data = 0}, - {.id = "NIC2600", .driver_data = 0}, - {.id = "NIC2700", .driver_data = 0}, - {.id = ""} +static const struct pnp_device_id device_ids[] = { + { .id = "NIC1900" }, + { .id = "NIC2400" }, + { .id = "NIC2500" }, + { .id = "NIC2600" }, + { .id = "NIC2700" }, + { } }; MODULE_DEVICE_TABLE(pnp, device_ids); From 88bf4a3d7d47a971449541957018ae54ca87f80f Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Fri, 17 Jul 2026 18:05:30 +0800 Subject: [PATCH 067/135] misc: bcm-vk: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() calls. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260717100533.601899-2-panchuang@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/bcm-vk/bcm_vk_dev.c | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/drivers/misc/bcm-vk/bcm_vk_dev.c b/drivers/misc/bcm-vk/bcm_vk_dev.c index 5773ffb46f0f..54b40b4d556d 100644 --- a/drivers/misc/bcm-vk/bcm_vk_dev.c +++ b/drivers/misc/bcm-vk/bcm_vk_dev.c @@ -1370,21 +1370,15 @@ static int bcm_vk_probe(struct pci_dev *pdev, const struct pci_device_id *ent) err = devm_request_irq(dev, pci_irq_vector(pdev, vk->num_irqs), bcm_vk_msgq_irqhandler, IRQF_SHARED, DRV_MODULE_NAME, vk); - if (err) { - dev_err(dev, "failed to request msgq IRQ %d for MSIX %d\n", - pdev->irq + vk->num_irqs, vk->num_irqs + 1); + if (err) goto err_irq; - } } /* one irq for notification from VK */ err = devm_request_irq(dev, pci_irq_vector(pdev, vk->num_irqs), bcm_vk_notf_irqhandler, IRQF_SHARED, DRV_MODULE_NAME, vk); - if (err) { - dev_err(dev, "failed to request notf IRQ %d for MSIX %d\n", - pdev->irq + vk->num_irqs, vk->num_irqs + 1); + if (err) goto err_irq; - } vk->num_irqs++; for (i = 0; @@ -1393,11 +1387,8 @@ static int bcm_vk_probe(struct pci_dev *pdev, const struct pci_device_id *ent) err = devm_request_irq(dev, pci_irq_vector(pdev, vk->num_irqs), bcm_vk_tty_irqhandler, IRQF_SHARED, DRV_MODULE_NAME, vk); - if (err) { - dev_err(dev, "failed request tty IRQ %d for MSIX %d\n", - pdev->irq + vk->num_irqs, vk->num_irqs + 1); + if (err) goto err_irq; - } bcm_vk_tty_set_irq_enabled(vk, i); } From 7db324fbee6c5fe19ea99c8183d2e1cb6102f109 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Fri, 17 Jul 2026 18:05:31 +0800 Subject: [PATCH 068/135] misc: Remove redundant dev_err()/dev_err_probe() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() and devm_request_threaded_irq() automatically log detailed error messages on failure. Remove the now-redundant driver-specific dev_err() and dev_err_probe() calls. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260717100533.601899-3-panchuang@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/hi6421v600-irq.c | 5 +---- drivers/misc/mrvl_cn10k_dpi.c | 4 +--- drivers/misc/tps6594-esm.c | 2 +- drivers/misc/tps6594-pfsm.c | 2 +- drivers/misc/xilinx_sdfec.c | 4 +--- 5 files changed, 5 insertions(+), 12 deletions(-) diff --git a/drivers/misc/hi6421v600-irq.c b/drivers/misc/hi6421v600-irq.c index 5ba40222eb12..5f65ca91b342 100644 --- a/drivers/misc/hi6421v600-irq.c +++ b/drivers/misc/hi6421v600-irq.c @@ -274,11 +274,8 @@ static int hi6421v600_irq_probe(struct platform_device *pdev) NULL, IRQF_TRIGGER_LOW | IRQF_SHARED | IRQF_NO_SUSPEND, "pmic", priv); - if (ret < 0) { - dev_err(dev, "Failed to start IRQ handling thread: error %d\n", - ret); + if (ret < 0) return ret; - } return 0; } diff --git a/drivers/misc/mrvl_cn10k_dpi.c b/drivers/misc/mrvl_cn10k_dpi.c index 7d5433121ff6..d26c53af05eb 100644 --- a/drivers/misc/mrvl_cn10k_dpi.c +++ b/drivers/misc/mrvl_cn10k_dpi.c @@ -470,10 +470,8 @@ static int dpi_irq_init(struct dpipf *dpi) ret = devm_request_irq(dev, pci_irq_vector(pdev, DPI_MBOX_PF_VF_INT_IDX), dpi_mbox_intr_handler, 0, "dpi-mbox", dpi); - if (ret) { - dev_err(dev, "DPI: request_irq failed for mbox; err=%d\n", ret); + if (ret) return ret; - } dpi_reg_write(dpi, DPI_MBOX_VF_PF_INT_ENA_W1S, GENMASK_ULL(31, 0)); diff --git a/drivers/misc/tps6594-esm.c b/drivers/misc/tps6594-esm.c index 2fbd3fbdf713..50e121669c6d 100644 --- a/drivers/misc/tps6594-esm.c +++ b/drivers/misc/tps6594-esm.c @@ -62,7 +62,7 @@ static int tps6594_esm_probe(struct platform_device *pdev) tps6594_esm_isr, IRQF_ONESHOT, pdev->resource[i].name, pdev); if (ret) - return dev_err_probe(dev, ret, "Failed to request irq\n"); + return ret; } ret = regmap_set_bits(tps->regmap, TPS6594_REG_ESM_SOC_MODE_CFG, diff --git a/drivers/misc/tps6594-pfsm.c b/drivers/misc/tps6594-pfsm.c index 44fa81d6cec2..c3a0d6367f2c 100644 --- a/drivers/misc/tps6594-pfsm.c +++ b/drivers/misc/tps6594-pfsm.c @@ -308,7 +308,7 @@ static int tps6594_pfsm_probe(struct platform_device *pdev) tps6594_pfsm_isr, IRQF_ONESHOT, pdev->resource[i].name, pdev); if (ret) - return dev_err_probe(dev, ret, "Failed to request irq\n"); + return ret; } platform_set_drvdata(pdev, pfsm); diff --git a/drivers/misc/xilinx_sdfec.c b/drivers/misc/xilinx_sdfec.c index 3135ba3a58ee..fe7bea3b14bf 100644 --- a/drivers/misc/xilinx_sdfec.c +++ b/drivers/misc/xilinx_sdfec.c @@ -1390,10 +1390,8 @@ static int xsdfec_probe(struct platform_device *pdev) err = devm_request_threaded_irq(dev, xsdfec->irq, NULL, xsdfec_irq_thread, IRQF_ONESHOT, "xilinx-sdfec16", xsdfec); - if (err < 0) { - dev_err(dev, "unable to request IRQ%d", xsdfec->irq); + if (err < 0) goto err_xsdfec_dev; - } } err = ida_alloc(&dev_nrs, GFP_KERNEL); From cfefdadbae4b4c8c353ab5f397d3bf77a71279b4 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Fri, 17 Jul 2026 18:05:32 +0800 Subject: [PATCH 069/135] mei: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_threaded_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() calls. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260717100533.601899-4-panchuang@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/gsc-me.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/misc/mei/gsc-me.c b/drivers/misc/mei/gsc-me.c index 73d5beeb9c34..376840b2c354 100644 --- a/drivers/misc/mei/gsc-me.c +++ b/drivers/misc/mei/gsc-me.c @@ -101,10 +101,8 @@ static int mei_gsc_probe(struct auxiliary_device *aux_dev, mei_me_irq_quick_handler, mei_me_irq_thread_handler, IRQF_ONESHOT, KBUILD_MODNAME, dev); - if (ret) { - dev_err(device, "irq register failed %d\n", ret); + if (ret) goto err; - } } ret = mei_register(dev, device); From 2847d9ab088bb559f4f03070f6ad103070362b25 Mon Sep 17 00:00:00 2001 From: Jahnavi MN Date: Thu, 16 Jul 2026 08:37:43 +0000 Subject: [PATCH 070/135] rust_binder: Add dynamic debug logging mask Implement a dynamic debug logging mask (`debug_mask`) for the `rust_binder` module to allow dynamic runtime configuration of log levels. This enables parity with the legacy C driver's debug mask. Since the Rust `module!` macro in the current kernel build does not yet support declaring module parameters directly in Rust, we define the `debug_mask` variable in Rust as an `Atomic` exported via FFI using `#[no_mangle]`, and link to it as `extern` in a C companion file to expose it to the kernel runtime. To verify the setup, instrument process lifecycle events (open, flush, and release) in `process.rs` under the new `BINDER_DEBUG_OPEN_CLOSE` logging mask. These entry-point events are chosen for initial validation because they represent the start of the Binder lifecycle and occur at low frequency, allowing simple runtime verification of the dynamic toggle without log noise. Reviewed-by: Carlos Llamas Reviewed-by: Alice Ryhl Signed-off-by: Jahnavi MN Link: https://patch.msgid.link/20260716-rust_binder_debug_mask-v4-1-3d7436c2d2f2@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/debug.rs | 76 ++++++++++++++++++++++ drivers/android/binder/process.rs | 7 +- drivers/android/binder/rust_binder_main.rs | 2 + drivers/android/binder/rust_binderfs.c | 3 + rust/kernel/task.rs | 7 ++ 5 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 drivers/android/binder/debug.rs diff --git a/drivers/android/binder/debug.rs b/drivers/android/binder/debug.rs new file mode 100644 index 000000000000..824b10c004c3 --- /dev/null +++ b/drivers/android/binder/debug.rs @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2026 Google LLC. + +//! Binder debugging helpers. + +#![allow(dead_code)] + +use kernel::bits::bit_u32; +use kernel::sync::atomic::Atomic; + +kernel::impl_flags!( + /// Represents multiple debug mask flags. + #[derive(Debug, Clone, Default, Copy, PartialEq, Eq)] + pub struct DebugMasks(u32); + + /// Represents a single debug mask category. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum DebugMask { + UserError = bit_u32(0), + FailedTransaction = bit_u32(1), + DeadTransaction = bit_u32(2), + OpenClose = bit_u32(3), + DeadBinder = bit_u32(4), + DeathNotification = bit_u32(5), + ReadWrite = bit_u32(6), + UserRefs = bit_u32(7), + Threads = bit_u32(8), + Transaction = bit_u32(9), + TransactionComplete = bit_u32(10), + FreeBuffer = bit_u32(11), + InternalRefs = bit_u32(12), + PriorityCap = bit_u32(13), + Spinlocks = bit_u32(14), + } +); + +#[no_mangle] +pub(crate) static rust_binder_debug_mask: Atomic = Atomic::new( + (DebugMask::UserError as u32) + | (DebugMask::FailedTransaction as u32) + | (DebugMask::DeadTransaction as u32), +); + +/// Checks if the given debug logging category is enabled in the mask. +pub(crate) fn debug_mask_enabled(mask: DebugMask) -> bool { + let current_mask = rust_binder_debug_mask.load(kernel::sync::atomic::Relaxed); + DebugMasks(current_mask).contains(mask) +} + +/// Prints a debug log if the specified mask category is enabled. +#[macro_export] +macro_rules! binder_debug { + // Rule to explicitly specify a PID (used in kworkers). + (pid=$pid:expr, $mask:ident, $($arg:tt)*) => { + if $crate::debug::debug_mask_enabled($crate::debug::DebugMask::$mask) { + kernel::pr_info!( + "{}: {}\n", + $pid, + kernel::prelude::fmt!($($arg)*) + ); + } + }; + + // Default rule (automatically prepends "PID:TID" of the current calling thread). + ($mask:ident, $($arg:tt)*) => { + if $crate::debug::debug_mask_enabled($crate::debug::DebugMask::$mask) { + let thread = kernel::current!(); + kernel::pr_info!( + "{}:{} {}\n", + thread.tgid(), + thread.pid(), + kernel::prelude::fmt!($($arg)*) + ); + } + }; +} diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs index 0555c4bd503e..5240686324cf 100644 --- a/drivers/android/binder/process.rs +++ b/drivers/android/binder/process.rs @@ -1341,6 +1341,7 @@ impl Process { } fn deferred_flush(&self) { + binder_debug!(pid = self.task.pid(), OpenClose, "flushing process"); let inner = self.inner.lock(); for thread in inner.threads.values() { thread.exit_looper(); @@ -1348,6 +1349,8 @@ impl Process { } fn deferred_release(self: Arc) { + binder_debug!(pid = self.task.pid(), OpenClose, "releasing process"); + let is_manager = { let mut inner = self.inner.lock(); inner.is_dead = true; @@ -1641,7 +1644,9 @@ impl Process { /// The file operations supported by `Process`. impl Process { pub(crate) fn open(ctx: ArcBorrow<'_, Context>, file: &File) -> Result> { - Self::new(ctx.into(), ARef::from(file.cred())) + let proc = Self::new(ctx.into(), ARef::from(file.cred()))?; + binder_debug!(OpenClose, "opened process"); + Ok(proc) } pub(crate) fn release(this: Arc, _file: &File) { diff --git a/drivers/android/binder/rust_binder_main.rs b/drivers/android/binder/rust_binder_main.rs index 432390aab25b..29829cb210a4 100644 --- a/drivers/android/binder/rust_binder_main.rs +++ b/drivers/android/binder/rust_binder_main.rs @@ -31,6 +31,8 @@ mod allocation; mod context; mod deferred_close; mod defs; +#[macro_use] +mod debug; mod error; mod node; mod page_range; diff --git a/drivers/android/binder/rust_binderfs.c b/drivers/android/binder/rust_binderfs.c index ade1c4d92499..300cc65562d1 100644 --- a/drivers/android/binder/rust_binderfs.c +++ b/drivers/android/binder/rust_binderfs.c @@ -51,6 +51,9 @@ DEFINE_SHOW_ATTRIBUTE(rust_binder_proc); char *rust_binder_devices_param = CONFIG_ANDROID_BINDER_DEVICES; module_param_named(rust_devices, rust_binder_devices_param, charp, 0444); +extern u32 rust_binder_debug_mask; +module_param_named(debug_mask, rust_binder_debug_mask, uint, 0644); + static dev_t binderfs_dev; static DEFINE_MUTEX(binderfs_minors_mutex); static DEFINE_IDA(binderfs_minors); diff --git a/rust/kernel/task.rs b/rust/kernel/task.rs index 38273f4eedb5..1b290c61714d 100644 --- a/rust/kernel/task.rs +++ b/rust/kernel/task.rs @@ -210,6 +210,13 @@ impl Task { unsafe { *ptr::addr_of!((*self.as_ptr()).pid) } } + /// Returns the TGID (Thread Group ID / Process ID) of the given task. + pub fn tgid(&self) -> Pid { + // SAFETY: The tgid of a task never changes after initialization, so reading this field is + // not a data race. + unsafe { *ptr::addr_of!((*self.as_ptr()).tgid) } + } + /// Returns the UID of the given task. #[inline] pub fn uid(&self) -> Kuid { From d8f87e4eded64b9e27f6c0f815b71489f29cb95c Mon Sep 17 00:00:00 2001 From: Jahnavi MN Date: Thu, 16 Jul 2026 08:37:44 +0000 Subject: [PATCH 071/135] rust_binder: Implement BINDER_DEBUG_USER_ERROR for freezer-related operation This adds dynamic debug logs for: - Requesting freeze notifications on invalid references, duplicate cookies, or already active registrations. - Completing freeze notifications that are not pending or not found. - Clearing freeze notifications on invalid references, inactive notifications, or cookie mismatches. Reviewed-by: Carlos Llamas Reviewed-by: Alice Ryhl Signed-off-by: Jahnavi MN Link: https://patch.msgid.link/20260716-rust_binder_debug_mask-v4-2-3d7436c2d2f2@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/freeze.rs | 40 ++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/drivers/android/binder/freeze.rs b/drivers/android/binder/freeze.rs index f43388ed6ae2..318a9d2bb261 100644 --- a/drivers/android/binder/freeze.rs +++ b/drivers/android/binder/freeze.rs @@ -189,12 +189,15 @@ impl Process { info = match node_refs.by_handle.get_mut(&handle) { Some(info) => info, None => { - pr_warn!("BC_REQUEST_FREEZE_NOTIFICATION invalid ref {}\n", handle); + binder_debug!( + UserError, + "BC_REQUEST_FREEZE_NOTIFICATION invalid ref {handle}" + ); return Err(EINVAL); } }; if info.freeze().is_some() { - pr_warn!("BC_REQUEST_FREEZE_NOTIFICATION already set\n"); + binder_debug!(UserError, "BC_REQUEST_FREEZE_NOTIFICATION already set"); return Err(EINVAL); } let node_ref = info.node_ref(); @@ -202,7 +205,7 @@ impl Process { if let rbtree::Entry::Occupied(ref dupe) = freeze_entry { if !dupe.get().allow_duplicate(&node_ref.node) { - pr_warn!("BC_REQUEST_FREEZE_NOTIFICATION duplicate cookie\n"); + binder_debug!(UserError, "BC_REQUEST_FREEZE_NOTIFICATION duplicate cookie"); return Err(EINVAL); } } @@ -267,7 +270,11 @@ impl Process { let mut node_refs_guard = self.node_refs.lock(); let node_refs = &mut *node_refs_guard; let Some(freeze) = node_refs.freeze_listeners.get_mut(&cookie) else { - pr_warn!("BC_FREEZE_NOTIFICATION_DONE {:016x} not found\n", cookie.0); + binder_debug!( + UserError, + "BC_FREEZE_NOTIFICATION_DONE {:016x} not found", + cookie.0 + ); return Err(EINVAL); }; let mut clear_msg = None; @@ -277,8 +284,9 @@ impl Process { freeze.num_cleared_duplicates += 1; } else { if !freeze.is_pending { - pr_warn!( - "BC_FREEZE_NOTIFICATION_DONE {:016x} not pending\n", + binder_debug!( + UserError, + "BC_FREEZE_NOTIFICATION_DONE {:016x} not pending", cookie.0 ); return Err(EINVAL); @@ -307,19 +315,31 @@ impl Process { let mut node_refs_guard = self.node_refs.lock(); let node_refs = &mut *node_refs_guard; let Some(info) = node_refs.by_handle.get_mut(&handle) else { - pr_warn!("BC_CLEAR_FREEZE_NOTIFICATION invalid ref {}\n", handle); + binder_debug!( + UserError, + "BC_CLEAR_FREEZE_NOTIFICATION invalid ref {handle}" + ); return Err(EINVAL); }; let Some(info_cookie) = info.freeze() else { - pr_warn!("BC_CLEAR_FREEZE_NOTIFICATION freeze notification not active\n"); + binder_debug!( + UserError, + "BC_CLEAR_FREEZE_NOTIFICATION freeze notification not active" + ); return Err(EINVAL); }; if *info_cookie != cookie { - pr_warn!("BC_CLEAR_FREEZE_NOTIFICATION freeze notification cookie mismatch\n"); + binder_debug!( + UserError, + "BC_CLEAR_FREEZE_NOTIFICATION freeze notification cookie mismatch" + ); return Err(EINVAL); } let Some(listener) = node_refs.freeze_listeners.get_mut(&cookie) else { - pr_warn!("BC_CLEAR_FREEZE_NOTIFICATION invalid cookie {}\n", handle); + binder_debug!( + UserError, + "BC_CLEAR_FREEZE_NOTIFICATION invalid cookie {handle}" + ); return Err(EINVAL); }; listener.is_clearing = true; From 11071c63a91eefaef25d602697fe04fc2b7748e8 Mon Sep 17 00:00:00 2001 From: Jahnavi MN Date: Thu, 16 Jul 2026 08:37:45 +0000 Subject: [PATCH 072/135] rust_binder: Implement BINDER_DEBUG_USER_ERROR for refcounting and death notifications This adds dynamic debug logs for: - Decrementing handle reference counts that are already zero. - Mismatched reference states (calling inc_ref_done with no active inc_refs, or using a weak reference as a strong reference). - Requesting or clearing death notifications on invalid references, already active notifications, or with mismatched cookies. Reviewed-by: Carlos Llamas Reviewed-by: Alice Ryhl Signed-off-by: Jahnavi MN Link: https://patch.msgid.link/20260716-rust_binder_debug_mask-v4-3-3d7436c2d2f2@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/node.rs | 10 +++++---- drivers/android/binder/process.rs | 35 ++++++++++++++++++++++++------- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/drivers/android/binder/node.rs b/drivers/android/binder/node.rs index 59c5ab747bf4..fefa723d13c4 100644 --- a/drivers/android/binder/node.rs +++ b/drivers/android/binder/node.rs @@ -345,7 +345,7 @@ impl Node { ) -> Option> { let inner = self.inner.access_mut(owner_inner); if inner.active_inc_refs == 0 { - pr_err!("inc_ref_done called when no active inc_refs"); + binder_debug!(UserError, "inc_ref_done called when no active inc_refs"); return None; } @@ -821,6 +821,7 @@ impl NodeRef { pub(crate) fn clone(&self, strong: bool) -> Result { if strong && self.strong_count == 0 { + binder_debug!(UserError, "tried to use weak ref as strong ref"); return Err(EINVAL); } Ok(self @@ -861,9 +862,10 @@ impl NodeRef { *count += 1; } else { if *count == 0 { - pr_warn!( - "pid {} performed invalid decrement on ref\n", - kernel::current!().pid() + binder_debug!( + UserError, + "performed invalid {} decrement on ref", + if strong { "strong" } else { "weak" } ); return false; } diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs index 5240686324cf..1d3a71292de0 100644 --- a/drivers/android/binder/process.rs +++ b/drivers/android/binder/process.rs @@ -912,7 +912,13 @@ impl Process { } Ok(node_ref) } else { - Ok(self.get_node_from_handle(handle, true)?) + match self.get_node_from_handle(handle, true) { + Ok(node_ref) => Ok(node_ref), + Err(err) => { + binder_debug!(UserError, "got transaction to invalid handle {handle}"); + Err(err.into()) + } + } } } @@ -997,7 +1003,7 @@ impl Process { } else { // All refs are cleared in process exit, so this warning is expected in that case. if !self.inner.lock().is_dead { - pr_warn!("{}: no such ref {handle}\n", self.pid_in_current_ns()); + binder_debug!(UserError, "no such ref {handle}"); } } Ok(()) @@ -1250,13 +1256,19 @@ impl Process { })?; let mut refs = self.node_refs.lock(); let Some(info) = refs.by_handle.get_mut(&handle) else { - pr_warn!("BC_REQUEST_DEATH_NOTIFICATION invalid ref {handle}\n"); + binder_debug!( + UserError, + "BC_REQUEST_DEATH_NOTIFICATION invalid ref {handle}" + ); return Ok(()); }; // Nothing to do if there is already a death notification request for this handle. if info.death().is_some() { - pr_warn!("BC_REQUEST_DEATH_NOTIFICATION death notification already set\n"); + binder_debug!( + UserError, + "BC_REQUEST_DEATH_NOTIFICATION death notification already set" + ); return Ok(()); } @@ -1293,17 +1305,26 @@ impl Process { let mut refs = self.node_refs.lock(); let Some(info) = refs.by_handle.get_mut(&handle) else { - pr_warn!("BC_CLEAR_DEATH_NOTIFICATION invalid ref {handle}\n"); + binder_debug!( + UserError, + "BC_CLEAR_DEATH_NOTIFICATION invalid ref {handle}" + ); return Ok(()); }; let Some(death) = info.death().take() else { - pr_warn!("BC_CLEAR_DEATH_NOTIFICATION death notification not active\n"); + binder_debug!( + UserError, + "BC_CLEAR_DEATH_NOTIFICATION death notification not active" + ); return Ok(()); }; if death.cookie != cookie { *info.death() = Some(death); - pr_warn!("BC_CLEAR_DEATH_NOTIFICATION death notification cookie mismatch\n"); + binder_debug!( + UserError, + "BC_CLEAR_DEATH_NOTIFICATION death notification cookie mismatch" + ); return Ok(()); } From e49c203bce47d96456138a8a0819284c6dabbd84 Mon Sep 17 00:00:00 2001 From: Jahnavi MN Date: Thu, 16 Jul 2026 08:37:46 +0000 Subject: [PATCH 073/135] rust_binder: Implement BINDER_DEBUG_USER_ERROR for transaction parsing failures This adds dynamic debug logs in `thread.rs` for: - File descriptor array (FDA) parent offset and parent buffer address alignment misalignments. - Memory copy, write, and translation failures during transaction serialization (including out-of-bounds pointer fixups). - Incoming transactions or replies that do not match the expected thread calling stack (such as out-of-order replies). Reviewed-by: Carlos Llamas Reviewed-by: Alice Ryhl Signed-off-by: Jahnavi MN Link: https://patch.msgid.link/20260716-rust_binder_debug_mask-v4-4-3d7436c2d2f2@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/thread.rs | 54 +++++++++++++++++++------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/drivers/android/binder/thread.rs b/drivers/android/binder/thread.rs index 19f881948a84..9f0178a13d6f 100644 --- a/drivers/android/binder/thread.rs +++ b/drivers/android/binder/thread.rs @@ -728,11 +728,12 @@ impl Thread { let alloc_offset = match sg_state.unused_buffer_space.claim_next(obj_length) { Ok(alloc_offset) => alloc_offset, Err(err) => { - pr_warn!( - "Failed to claim space for a BINDER_TYPE_PTR. (offset: {}, limit: {}, size: {})", + binder_debug!( + UserError, + "failed to claim space for a BINDER_TYPE_PTR (offset: {}, limit: {}, size: {})", sg_state.unused_buffer_space.offset, sg_state.unused_buffer_space.limit, - obj_length, + obj_length ); return Err(err.into()); } @@ -811,6 +812,7 @@ impl Thread { let fds_len = num_fds.checked_mul(size_of::()).ok_or(EINVAL)?; if !is_aligned(parent_offset, size_of::()) { + binder_debug!(UserError, "FDA parent offset not aligned correctly"); return Err(EINVAL.into()); } @@ -829,6 +831,7 @@ impl Thread { }; if !is_aligned(parent_entry.sender_uaddr, size_of::()) { + binder_debug!(UserError, "FDA parent buffer not aligned correctly"); return Err(EINVAL.into()); } @@ -912,12 +915,9 @@ impl Thread { let target_offset_end = fixup_offset.checked_add(fixup_len).ok_or(EINVAL)?; if fixup_offset < end_of_previous_fixup || offset_end < target_offset_end { - pr_warn!( - "Fixups oob {} {} {} {}", - fixup_offset, - end_of_previous_fixup, - offset_end, - target_offset_end + binder_debug!( + UserError, + "fixups oob {fixup_offset} {end_of_previous_fixup} {offset_end} {target_offset_end}" ); return Err(EINVAL.into()); } @@ -925,18 +925,21 @@ impl Thread { let copy_off = end_of_previous_fixup; let copy_len = fixup_offset - end_of_previous_fixup; if let Err(err) = alloc.copy_into(&mut reader, copy_off, copy_len) { - pr_warn!("Failed copying into alloc: {:?}", err); + binder_debug!(UserError, "failed copying into alloc: {err:?}"); return Err(err.into()); } if let PointerFixupEntry::Fixup { pointer_value, .. } = fixup { let res = alloc.write::(fixup_offset, pointer_value); if let Err(err) = res { - pr_warn!("Failed copying ptr into alloc: {:?}", err); + binder_debug!(UserError, "failed copying ptr into alloc: {err:?}"); return Err(err.into()); } } if let Err(err) = reader.skip(fixup_len) { - pr_warn!("Failed skipping {} from reader: {:?}", fixup_len, err); + binder_debug!( + UserError, + "failed skipping {fixup_len} from reader: {err:?}" + ); return Err(err.into()); } end_of_previous_fixup = target_offset_end; @@ -944,7 +947,7 @@ impl Thread { let copy_off = end_of_previous_fixup; let copy_len = offset_end - end_of_previous_fixup; if let Err(err) = alloc.copy_into(&mut reader, copy_off, copy_len) { - pr_warn!("Failed copying remainder into alloc: {:?}", err); + binder_debug!(UserError, "failed copying remainder into alloc: {err:?}"); return Err(err.into()); } } @@ -1048,7 +1051,7 @@ impl Thread { let offset: usize = offset.try_into().map_err(|_| EINVAL)?; if offset < end_of_previous_object || !is_aligned(offset, size_of::()) { - pr_warn!("Got transaction with invalid offset."); + binder_debug!(UserError, "got transaction with invalid offset"); return Err(EINVAL.into()); } @@ -1073,7 +1076,7 @@ impl Thread { ) { Ok(()) => end_of_previous_object = offset + object.size(), Err(err) => { - pr_warn!("Error while translating object."); + binder_debug!(UserError, "error while translating object: {err:?}"); return Err(err); } } @@ -1093,15 +1096,12 @@ impl Thread { )?; if let Some(sg_state) = sg_state.as_mut() { - if let Err(err) = self.apply_sg(&mut alloc, sg_state) { - pr_warn!("Failure in apply_sg: {:?}", err); - return Err(err); - } + self.apply_sg(&mut alloc, sg_state)?; } if let Some((off_out, secctx)) = secctx.as_mut() { if let Err(err) = alloc.write(secctx_off, secctx.as_bytes()) { - pr_warn!("Failed to write security context: {:?}", err); + binder_debug!(UserError, "failed to write security context: {err:?}"); return Err(err.into()); } **off_out = secctx_off; @@ -1303,7 +1303,7 @@ impl Thread { { let mut inner = self.inner.lock(); if !transaction.is_stacked_on(&inner.current_transaction) { - pr_warn!("Transaction stack changed during transaction!"); + binder_debug!(UserError, "got new transaction with bad transaction stack"); return Err(EINVAL.into()); } inner.current_transaction = Some(transaction.clone_arc()); @@ -1326,8 +1326,18 @@ impl Thread { } fn reply_inner(self: &Arc, info: &mut TransactionInfo) -> BinderResult { - let orig = self.inner.lock().pop_transaction_to_reply(self)?; + let orig = match self.inner.lock().pop_transaction_to_reply(self) { + Ok(orig) => orig, + Err(err) => { + binder_debug!(UserError, "got reply transaction with no transaction stack"); + return Err(err.into()); + } + }; if !orig.from.is_current_transaction(&orig) { + binder_debug!( + UserError, + "got reply transaction with bad transaction stack" + ); return Err(EINVAL.into()); } From c61f3ad2213cfb1d9f383d3b083fb7f1472cc657 Mon Sep 17 00:00:00 2001 From: Jahnavi MN Date: Thu, 16 Jul 2026 08:37:47 +0000 Subject: [PATCH 074/135] rust_binder: Implement BINDER_DEBUG_FAILED_TRANSACTION This adds dynamic debug logs for: - Failed replies, target process deaths, and error code deliveries. - Detailed transaction failure diagnostics (including sender/receiver PIDs, TIDs, transaction IDs, buffer sizes, and error codes). Reviewed-by: Carlos Llamas Reviewed-by: Alice Ryhl Signed-off-by: Jahnavi MN Link: https://patch.msgid.link/20260716-rust_binder_debug_mask-v4-5-3d7436c2d2f2@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/thread.rs | 21 ++++++++++++++++----- drivers/android/binder/transaction.rs | 8 ++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/drivers/android/binder/thread.rs b/drivers/android/binder/thread.rs index 9f0178a13d6f..38b90c79c057 100644 --- a/drivers/android/binder/thread.rs +++ b/drivers/android/binder/thread.rs @@ -1275,11 +1275,22 @@ impl Thread { } } - pr_warn!( - "{}:{} transaction to {} failed: {err:?}", - info.from_pid, - info.from_tid, - info.to_pid + binder_debug!( + FailedTransaction, + "transaction {} to {}:{} failed {:?}, code {} size {}-{}", + if info.is_reply { + "reply" + } else if info.is_oneway() { + "async" + } else { + "call" + }, + info.to_pid, + info.to_tid, + err, + info.code, + info.data_size, + info.offsets_size ); } } diff --git a/drivers/android/binder/transaction.rs b/drivers/android/binder/transaction.rs index afef5b46eac2..069c792d2200 100644 --- a/drivers/android/binder/transaction.rs +++ b/drivers/android/binder/transaction.rs @@ -404,6 +404,14 @@ impl DeliverToRead for Transaction { } else { // On failure to process the list, we send a reply back to the sender and ignore the // transaction on the recipient. + binder_debug!( + FailedTransaction, + "transaction {} to {} failed, fd fixups failed, size {}-{}", + self.debug_id, + self.to.task.pid(), + self.data_size, + self.offsets_size + ); return Ok(true); }; From 7ddb9f5d4564103d79c82f89f3d356629631add3 Mon Sep 17 00:00:00 2001 From: Jahnavi MN Date: Thu, 16 Jul 2026 08:37:48 +0000 Subject: [PATCH 075/135] rust_binder: Implement BINDER_DEBUG_DEATH_NOTIFICATION This adds dynamic debug logs for: - Memory allocation (OOM) failures when requesting death notifications - Registration and cancellation lifecycle events (BC_REQUEST / BC_CLEAR) - Delivery of death notification events to userspace (BR_DEAD_BINDER) Reviewed-by: Carlos Llamas Reviewed-by: Alice Ryhl Signed-off-by: Jahnavi MN Link: https://patch.msgid.link/20260716-rust_binder_debug_mask-v4-6-3d7436c2d2f2@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/node.rs | 5 +++++ drivers/android/binder/process.rs | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/drivers/android/binder/node.rs b/drivers/android/binder/node.rs index fefa723d13c4..8a87dc366aa9 100644 --- a/drivers/android/binder/node.rs +++ b/drivers/android/binder/node.rs @@ -1107,6 +1107,11 @@ impl DeliverToRead for NodeDeath { // We're still holding the inner lock, so it cannot be aborted while we insert it into // the delivered list. process_inner.death_delivered(self.clone()); + binder_debug!( + DeathNotification, + "sending death notification, cookie {:016x}", + cookie + ); BR_DEAD_BINDER }; diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs index 1d3a71292de0..eb2f08bec655 100644 --- a/drivers/android/binder/process.rs +++ b/drivers/android/binder/process.rs @@ -1253,6 +1253,10 @@ impl Process { // Queue BR_ERROR if we can't allocate memory for the death notification. let death = UniqueArc::new_uninit(GFP_KERNEL).inspect_err(|_| { thread.push_return_work(BR_ERROR); + binder_debug!( + DeathNotification, + "BC_REQUEST_DEATH_NOTIFICATION failed due to memory allocation failure" + ); })?; let mut refs = self.node_refs.lock(); let Some(info) = refs.by_handle.get_mut(&handle) else { @@ -1296,6 +1300,11 @@ impl Process { info.node_ref().node.add_death(death, &mut owner_inner); } } + binder_debug!( + DeathNotification, + "BC_REQUEST_DEATH_NOTIFICATION handle {handle} cookie {:016x}", + cookie + ); Ok(()) } @@ -1339,6 +1348,11 @@ impl Process { } } + binder_debug!( + DeathNotification, + "BC_CLEAR_DEATH_NOTIFICATION handle {handle} cookie {:016x}", + cookie + ); Ok(()) } From 5757ed4d9543ce1d5940c995e03fb0c6d32615cf Mon Sep 17 00:00:00 2001 From: Jahnavi MN Date: Thu, 16 Jul 2026 08:37:49 +0000 Subject: [PATCH 076/135] rust_binder: Implement BINDER_DEBUG_DEAD_TRANSACTION This adds dynamic debug logs for: - Releasing active transactions during thread stack unwinding. - Discarded transaction error codes when a thread exits. - Undelivered transaction acknowledgments (TRANSACTION_COMPLETE) upon thread exit. - Undelivered process death and freeze notifications when processes exit or die. - Undelivered transactions canceled due to target process death. We now store the process PID in `ThreadError`, `DeliverCode`, and `FreezeMessage` to ensure the correct PID is logged on cancellation. This is necessary because `cancel()` runs from background `kworkers`, which would otherwise print the wrong PID. Reviewed-by: Alice Ryhl Reviewed-by: Carlos Llamas Signed-off-by: Jahnavi MN Link: https://patch.msgid.link/20260716-rust_binder_debug_mask-v4-7-3d7436c2d2f2@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/freeze.rs | 24 ++++++++----- drivers/android/binder/node.rs | 9 ++++- drivers/android/binder/rust_binder_main.rs | 14 ++++++-- drivers/android/binder/thread.rs | 42 +++++++++++++++++----- drivers/android/binder/transaction.rs | 7 ++++ 5 files changed, 76 insertions(+), 20 deletions(-) diff --git a/drivers/android/binder/freeze.rs b/drivers/android/binder/freeze.rs index 318a9d2bb261..66912b4cb527 100644 --- a/drivers/android/binder/freeze.rs +++ b/drivers/android/binder/freeze.rs @@ -60,6 +60,7 @@ type UninitFM = UniqueArc>>; /// Represents a notification that the freeze state has changed. pub(crate) struct FreezeMessage { cookie: FreezeCookie, + pid: i32, } kernel::list::impl_list_arc_safe! { @@ -73,8 +74,8 @@ impl FreezeMessage { UniqueArc::new_uninit(flags) } - fn init(ua: UninitFM, cookie: FreezeCookie) -> DLArc { - match ua.pin_init_with(DTRWrap::new(FreezeMessage { cookie })) { + fn init(ua: UninitFM, cookie: FreezeCookie, pid: i32) -> DLArc { + match ua.pin_init_with(DTRWrap::new(FreezeMessage { cookie, pid })) { Ok(msg) => ListArc::from(msg), Err(err) => match err {}, } @@ -140,7 +141,14 @@ impl DeliverToRead for FreezeMessage { } } - fn cancel(self: DArc) {} + fn cancel(self: DArc) { + binder_debug!( + pid = self.pid, + DeadTransaction, + "undelivered freeze notification, {:016x}", + self.cookie.0 + ); + } fn should_sync_wakeup(&self) -> bool { false @@ -258,7 +266,7 @@ impl Process { } *info.freeze() = Some(cookie); - let msg = FreezeMessage::init(msg, cookie); + let msg = FreezeMessage::init(msg, cookie, self.task.pid()); drop(node_refs_guard); let _ = self.push_work(msg); Ok(()) @@ -279,7 +287,7 @@ impl Process { }; let mut clear_msg = None; if freeze.num_pending_duplicates > 0 { - clear_msg = Some(FreezeMessage::init(alloc, cookie)); + clear_msg = Some(FreezeMessage::init(alloc, cookie, self.task.pid())); freeze.num_pending_duplicates -= 1; freeze.num_cleared_duplicates += 1; } else { @@ -294,7 +302,7 @@ impl Process { let is_frozen = freeze.node.owner.inner.lock().is_frozen.is_fully_frozen(); if freeze.is_clearing || freeze.last_is_frozen != Some(is_frozen) { // Immediately send another FreezeMessage. - clear_msg = Some(FreezeMessage::init(alloc, cookie)); + clear_msg = Some(FreezeMessage::init(alloc, cookie, self.task.pid())); } freeze.is_pending = false; } @@ -347,7 +355,7 @@ impl Process { *info.freeze() = None; let mut msg = None; if !listener.is_pending { - msg = Some(FreezeMessage::init(alloc, cookie)); + msg = Some(FreezeMessage::init(alloc, cookie, self.task.pid())); } drop(node_refs_guard); @@ -427,7 +435,7 @@ impl Process { continue; }; let msg_alloc = FreezeMessage::new(GFP_KERNEL)?; - let msg = FreezeMessage::init(msg_alloc, cookie); + let msg = FreezeMessage::init(msg_alloc, cookie, proc.task.pid()); batch.push((proc, msg), GFP_KERNEL)?; } diff --git a/drivers/android/binder/node.rs b/drivers/android/binder/node.rs index 8a87dc366aa9..c73cdf82100f 100644 --- a/drivers/android/binder/node.rs +++ b/drivers/android/binder/node.rs @@ -1122,7 +1122,14 @@ impl DeliverToRead for NodeDeath { Ok(cmd != BR_DEAD_BINDER) } - fn cancel(self: DArc) {} + fn cancel(self: DArc) { + binder_debug!( + pid = self.process.task.pid(), + DeadTransaction, + "undelivered death notification, {:016x}", + self.cookie + ); + } fn should_sync_wakeup(&self) -> bool { false diff --git a/drivers/android/binder/rust_binder_main.rs b/drivers/android/binder/rust_binder_main.rs index 29829cb210a4..15c7b65928d8 100644 --- a/drivers/android/binder/rust_binder_main.rs +++ b/drivers/android/binder/rust_binder_main.rs @@ -221,6 +221,7 @@ impl DTRWrap { struct DeliverCode { code: u32, skip: Atomic, + pid: i32, } kernel::list::impl_list_arc_safe! { @@ -228,10 +229,11 @@ kernel::list::impl_list_arc_safe! { } impl DeliverCode { - fn new(code: u32) -> Self { + fn new(code: u32, pid: i32) -> Self { Self { code, skip: Atomic::new(false), + pid, } } @@ -256,7 +258,15 @@ impl DeliverToRead for DeliverCode { Ok(true) } - fn cancel(self: DArc) {} + fn cancel(self: DArc) { + if !self.skip.load(Relaxed) { + binder_debug!( + pid = self.pid, + DeadTransaction, + "undelivered TRANSACTION_COMPLETE" + ); + } + } fn should_sync_wakeup(&self) -> bool { false diff --git a/drivers/android/binder/thread.rs b/drivers/android/binder/thread.rs index 38b90c79c057..edc2613d13b5 100644 --- a/drivers/android/binder/thread.rs +++ b/drivers/android/binder/thread.rs @@ -279,7 +279,7 @@ const LOOPER_WAITING_PROC: u32 = 0x20; const LOOPER_POLL: u32 = 0x40; impl InnerThread { - fn new() -> Result { + fn new(pid: i32) -> Result { fn next_err_id() -> u32 { static EE_ID: Atomic = Atomic::new(0); EE_ID.fetch_add(1, Relaxed) @@ -290,8 +290,8 @@ impl InnerThread { looper_need_return: false, is_dead: false, process_work_list: false, - reply_work: ThreadError::try_new()?, - return_work: ThreadError::try_new()?, + reply_work: ThreadError::try_new(pid)?, + return_work: ThreadError::try_new(pid)?, work_list: List::new(), current_transaction: None, extended_error: ExtendedError::new(next_err_id(), BR_OK, 0), @@ -445,7 +445,7 @@ kernel::list::impl_list_item! { impl Thread { pub(crate) fn new(id: i32, process: Arc) -> Result> { - let inner = InnerThread::new()?; + let inner = InnerThread::new(process.task.pid())?; Arc::pin_init( try_pin_init!(Thread { @@ -1115,6 +1115,12 @@ impl Thread { let mut inner = thread.inner.lock(); inner.pop_transaction_to_reply(thread.as_ref()) } { + binder_debug!( + DeadTransaction, + "release transaction {} in, still active", + transaction.debug_id + ); + let reply = Err(BR_DEAD_REPLY); if !transaction .from @@ -1305,7 +1311,10 @@ impl Thread { // TODO: We need to ensure that there isn't a pending transaction in the work queue. How // could this happen? let top = self.top_of_transaction_stack()?; - let list_completion = DTRWrap::arc_try_new(DeliverCode::new(BR_TRANSACTION_COMPLETE))?; + let list_completion = DTRWrap::arc_try_new(DeliverCode::new( + BR_TRANSACTION_COMPLETE, + self.process.task.pid(), + ))?; let completion = list_completion.clone_arc(); let transaction = Transaction::new(node_ref, top, self, info)?; @@ -1357,7 +1366,10 @@ impl Thread { // We need to complete the transaction even if we cannot complete building the reply. let out = (|| -> BinderResult<_> { - let completion = DTRWrap::arc_try_new(DeliverCode::new(BR_TRANSACTION_COMPLETE))?; + let completion = DTRWrap::arc_try_new(DeliverCode::new( + BR_TRANSACTION_COMPLETE, + self.process.task.pid(), + ))?; let process = orig.from.process.clone(); let allow_fds = orig.flags & TF_ACCEPT_FDS != 0; let reply = Transaction::new_reply(self, process, info, allow_fds)?; @@ -1397,7 +1409,8 @@ impl Thread { } else { BR_TRANSACTION_COMPLETE }; - let list_completion = DTRWrap::arc_try_new(DeliverCode::new(code))?; + let list_completion = + DTRWrap::arc_try_new(DeliverCode::new(code, self.process.task.pid()))?; let completion = list_completion.clone_arc(); self.inner.lock().push_work(list_completion); match transaction.submit(info) { @@ -1653,14 +1666,16 @@ impl Thread { #[pin_data] struct ThreadError { error_code: Atomic, + pid: i32, #[pin] links_track: AtomicTracker, } impl ThreadError { - fn try_new() -> Result> { + fn try_new(pid: i32) -> Result> { DTRWrap::arc_pin_init(pin_init!(Self { error_code: Atomic::new(BR_OK), + pid, links_track <- AtomicTracker::new(), })) .map(ListArc::into_arc) @@ -1687,7 +1702,16 @@ impl DeliverToRead for ThreadError { Ok(true) } - fn cancel(self: DArc) {} + fn cancel(self: DArc) { + let code = self.error_code.load(Relaxed); + if code != BR_OK { + binder_debug!( + pid = self.pid, + DeadTransaction, + "undelivered TRANSACTION_ERROR: {code}" + ); + } + } fn should_sync_wakeup(&self) -> bool { false diff --git a/drivers/android/binder/transaction.rs b/drivers/android/binder/transaction.rs index 069c792d2200..0528070fe700 100644 --- a/drivers/android/binder/transaction.rs +++ b/drivers/android/binder/transaction.rs @@ -488,6 +488,13 @@ impl DeliverToRead for Transaction { if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 { let reply = Err(BR_DEAD_REPLY); self.from.deliver_reply(reply, &self, None); + } else { + binder_debug!( + pid = self.to.task.pid(), + DeadTransaction, + "undelivered transaction {}, process died", + self.debug_id + ); } self.drop_outstanding_txn(); From 2e70c06873c6e23441485d50b8ecac693e7f71c6 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Tue, 7 Jul 2026 10:06:49 +0000 Subject: [PATCH 077/135] rust: net: add rust/kernel/net to NETWORKING [GENERAL] To ensure that networking code can be found in a single shared place, add it to the relevant NETWORKING entry. The net.rs file is moved into the net/ directory so that it's included under the MAINTAINERS entry without needing a separate rust/kernel/net.rs entry. Reviewed-by: Carlos Llamas Acked-by: Jakub Kicinski Signed-off-by: Alice Ryhl Link: https://patch.msgid.link/20260707-binder-netlink-v7-1-42b40e4b1ac8@google.com Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 2 ++ rust/kernel/{net.rs => net/mod.rs} | 0 2 files changed, 2 insertions(+) rename rust/kernel/{net.rs => net/mod.rs} (100%) diff --git a/MAINTAINERS b/MAINTAINERS index 806bd2d80d15..be0af1665945 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18850,6 +18850,8 @@ F: include/uapi/linux/sctp.h F: lib/net_utils.c F: lib/random32.c F: net/ +F: rust/helpers/net/ +F: rust/kernel/net/ F: samples/pktgen/ F: tools/net/ F: tools/testing/selftests/net/ diff --git a/rust/kernel/net.rs b/rust/kernel/net/mod.rs similarity index 100% rename from rust/kernel/net.rs rename to rust/kernel/net/mod.rs From 5eaa5fbb6e6ce6d779fddf7aee42884d60990d40 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Tue, 7 Jul 2026 10:06:50 +0000 Subject: [PATCH 078/135] rust: netlink: add raw netlink abstraction This implements a safe and relatively simple API over the netlink API, that allows you to add different attributes to a netlink message and broadcast it. As the first user of this API only makes use of broadcast, only broadcast messages are supported here. This API is intended to be safe and to be easy to use in *generated* code. This is because netlink is generally used with yaml files that describe the underlying API, and the python generator outputs C code (or, soon, Rust code) that lets you use the API more easily. So for example, if there is a string field, the code generator will output a method that internall calls `put_string()` with the right attr type. Reviewed-by: Matthew Maurer Reviewed-by: Andrew Lunn Reviewed-by: Carlos Llamas Acked-by: Jakub Kicinski Signed-off-by: Alice Ryhl Link: https://patch.msgid.link/20260707-binder-netlink-v7-2-42b40e4b1ac8@google.com Signed-off-by: Greg Kroah-Hartman --- rust/bindings/bindings_helper.h | 3 + rust/helpers/helpers.c | 1 + rust/helpers/net/genetlink.c | 46 +++++ rust/kernel/net/mod.rs | 2 + rust/kernel/net/netlink.rs | 337 ++++++++++++++++++++++++++++++++ 5 files changed, 389 insertions(+) create mode 100644 rust/helpers/net/genetlink.c create mode 100644 rust/kernel/net/netlink.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 1124785e210b..ec96e811610d 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -93,6 +93,8 @@ #include #include #include +#include +#include #include /* @@ -110,6 +112,7 @@ const size_t RUST_CONST_HELPER_ARCH_SLAB_MINALIGN = ARCH_SLAB_MINALIGN; const size_t RUST_CONST_HELPER_ARCH_KMALLOC_MINALIGN = ARCH_KMALLOC_MINALIGN; const size_t RUST_CONST_HELPER_PAGE_SIZE = PAGE_SIZE; +const size_t RUST_CONST_HELPER_GENLMSG_DEFAULT_SIZE = GENLMSG_DEFAULT_SIZE; const gfp_t RUST_CONST_HELPER_GFP_ATOMIC = GFP_ATOMIC; const gfp_t RUST_CONST_HELPER_GFP_KERNEL = GFP_KERNEL; const gfp_t RUST_CONST_HELPER_GFP_KERNEL_ACCOUNT = GFP_KERNEL_ACCOUNT; diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index 998e31052e66..1d4ee51f576b 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -72,6 +72,7 @@ #include "maple_tree.c" #include "mm.c" #include "mutex.c" +#include "net/genetlink.c" #include "of.c" #include "page.c" #include "pci.c" diff --git a/rust/helpers/net/genetlink.c b/rust/helpers/net/genetlink.c new file mode 100644 index 000000000000..3530b69f6cf7 --- /dev/null +++ b/rust/helpers/net/genetlink.c @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Copyright (C) 2026 Google LLC. + */ + +#include + +#ifdef CONFIG_NET + +__rust_helper struct sk_buff *rust_helper_genlmsg_new(size_t payload, gfp_t flags) +{ + return genlmsg_new(payload, flags); +} + +__rust_helper +int rust_helper_genlmsg_multicast(const struct genl_family *family, + struct sk_buff *skb, u32 portid, + unsigned int group, gfp_t flags) +{ + return genlmsg_multicast(family, skb, portid, group, flags); +} + +__rust_helper void rust_helper_genlmsg_cancel(struct sk_buff *skb, void *hdr) +{ + genlmsg_cancel(skb, hdr); +} + +__rust_helper void rust_helper_genlmsg_end(struct sk_buff *skb, void *hdr) +{ + genlmsg_end(skb, hdr); +} + +__rust_helper void rust_helper_nlmsg_free(struct sk_buff *skb) +{ + nlmsg_free(skb); +} + +__rust_helper +int rust_helper_genl_has_listeners(const struct genl_family *family, + struct net *net, unsigned int group) +{ + return genl_has_listeners(family, net, group); +} + +#endif diff --git a/rust/kernel/net/mod.rs b/rust/kernel/net/mod.rs index fe415cb369d3..8ecae7577ed2 100644 --- a/rust/kernel/net/mod.rs +++ b/rust/kernel/net/mod.rs @@ -4,3 +4,5 @@ #[cfg(CONFIG_RUST_PHYLIB_ABSTRACTIONS)] pub mod phy; + +pub mod netlink; diff --git a/rust/kernel/net/netlink.rs b/rust/kernel/net/netlink.rs new file mode 100644 index 000000000000..22ef3dde36fa --- /dev/null +++ b/rust/kernel/net/netlink.rs @@ -0,0 +1,337 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2026 Google LLC. + +//! Rust support for generic netlink. +//! +//! Currently only supports exposing multicast groups. +//! +//! C header: [`include/net/genetlink.h`](srctree/include/net/genetlink.h) + +use kernel::{ + alloc::{self, AllocError}, + error::to_result, + prelude::*, + transmute::AsBytes, + types::Opaque, + ThisModule, +}; + +use core::{ + mem::ManuallyDrop, + ptr::NonNull, // +}; + +/// The default netlink message size. +pub const GENLMSG_DEFAULT_SIZE: usize = bindings::GENLMSG_DEFAULT_SIZE; + +/// A wrapper around `struct sk_buff` for generic netlink messages. +/// +/// This type is intended to be specific for buffers used with netlink only, and other usecases for +/// `struct sk_buff` are out-of-scope for this abstraction. +/// +/// # Invariants +/// +/// The pointer has ownership over a valid `sk_buff`. +pub struct NetlinkSkBuff { + skb: NonNull, +} + +impl NetlinkSkBuff { + /// Creates a new `NetlinkSkBuff` with the given size. + pub fn new(size: usize, flags: alloc::Flags) -> Result { + // SAFETY: `genlmsg_new` only requires its arguments to be valid integers. + let skb = unsafe { bindings::genlmsg_new(size, flags.as_raw()) }; + let skb = NonNull::new(skb).ok_or(AllocError)?; + Ok(NetlinkSkBuff { skb }) + } + + /// Puts a generic netlink header into the `NetlinkSkBuff`. + pub fn genlmsg_put( + self, + portid: u32, + seq: u32, + family: &'static Family, + cmd: u8, + ) -> Result { + let skb = self.skb.as_ptr(); + // SAFETY: The skb and family pointers are valid. + let hdr = unsafe { bindings::genlmsg_put(skb, portid, seq, family.as_raw(), 0, cmd) }; + let hdr = NonNull::new(hdr).ok_or(AllocError)?; + Ok(GenlMsg { skb: self, hdr }) + } +} + +impl Drop for NetlinkSkBuff { + fn drop(&mut self) { + // SAFETY: We have ownership over the `sk_buff`, so we may free it. + unsafe { bindings::nlmsg_free(self.skb.as_ptr()) } + } +} + +/// A generic netlink message being constructed. +/// +/// # Invariants +/// +/// `hdr` references the header in this netlink message. +pub struct GenlMsg { + skb: NetlinkSkBuff, + hdr: NonNull, +} + +impl GenlMsg { + /// Puts an attribute into the message. + #[inline] + fn put(&mut self, attrtype: c_int, value: &T) -> Result + where + T: ?Sized + AsBytes, + { + let skb = self.skb.skb.as_ptr(); + let len = size_of_val(value); + let ptr = core::ptr::from_ref(value).cast::(); + // SAFETY: `skb` is valid by `NetlinkSkBuff` type invariants, and the provided value is + // readable and initialized for its `size_of` bytes. + to_result(unsafe { bindings::nla_put(skb, attrtype, len as c_int, ptr) }) + } + + /// Puts a `u32` attribute into the message. + #[inline] + pub fn put_u32(&mut self, attrtype: c_int, value: u32) -> Result { + self.put(attrtype, &value) + } + + /// Puts a string attribute into the message. + #[inline] + pub fn put_string(&mut self, attrtype: c_int, value: &CStr) -> Result { + self.put(attrtype, value.to_bytes_with_nul()) + } + + /// Puts a flag attribute into the message. + #[inline] + pub fn put_flag(&mut self, attrtype: c_int) -> Result { + let skb = self.skb.skb.as_ptr(); + // SAFETY: `skb` is valid by `NetlinkSkBuff` type invariants, and a null pointer is valid + // when the length is zero. + to_result(unsafe { bindings::nla_put(skb, attrtype, 0, core::ptr::null()) }) + } + + /// Sends the generic netlink message as a multicast message. + #[inline] + pub fn multicast( + self, + family: &'static Family, + portid: u32, + group: u32, + flags: alloc::Flags, + ) -> Result { + let me = ManuallyDrop::new(self); + // SAFETY: The `skb` and `family` pointers are valid. We pass ownership of the `skb` to + // `genlmsg_multicast` by not dropping `self`. + unsafe { + bindings::genlmsg_end(me.skb.skb.as_ptr(), me.hdr.as_ptr()); + to_result(bindings::genlmsg_multicast( + family.as_raw(), + me.skb.skb.as_ptr(), + portid, + group, + flags.as_raw(), + )) + } + } +} +impl Drop for GenlMsg { + fn drop(&mut self) { + // SAFETY: The `hdr` pointer references the header of this generic netlink message. + unsafe { bindings::genlmsg_cancel(self.skb.skb.as_ptr(), self.hdr.as_ptr()) }; + } +} + +/// Flags for a generic netlink family. +struct FamilyFlags { + /// Whether the family supports network namespaces. + netnsok: bool, + /// Whether the family supports parallel operations. + parallel_ops: bool, +} + +impl FamilyFlags { + /// Converts the flags to the bitfield representation used by `genl_family`. + const fn into_bitfield(self) -> bindings::__BindgenBitfieldUnit<[u8; 1]> { + // The below shifts are verified correct by test_family_flags_bitfield() below. + // + // Although bindgen generates helpers to change bitfields based on the C headers, these + // helpers unfortunately can't be used in const context. Since `Family` needs to be filled + // out at build-time, we use this helper instead. + let mut bits = 0; + if self.netnsok { + bits |= 1 << 0; + } + if self.parallel_ops { + bits |= 1 << 1; + } + // Convert from little endian to the target's endianness. + bits = u8::from_le(bits); + // SAFETY: This bitfield is represented as an u8. + unsafe { core::mem::transmute::>(bits) } + } +} + +/// A generic netlink family. +#[repr(transparent)] +pub struct Family { + inner: Opaque, +} + +// SAFETY: The `Family` type is thread safe. +unsafe impl Sync for Family {} + +impl Family { + /// Creates a new `Family` instance. + /// + /// Intended to be used from const context only. Will panic if provided with invalid arguments. + /// + /// The name must be a nul-terminated string, but it is taken as `&[u8]` so that it can be used + /// more conveniently with the strings generated by bindgen. + pub const fn const_new( + module: &ThisModule, + name: &[u8], + version: u32, + mcgrps: &'static [MulticastGroup], + ) -> Family { + let n_mcgrps = mcgrps.len() as u8; + if n_mcgrps as usize != mcgrps.len() { + panic!("too many mcgrps"); + } + let mut genl_family = bindings::genl_family { + version, + _bitfield_1: FamilyFlags { + netnsok: true, + parallel_ops: true, + } + .into_bitfield(), + module: module.as_ptr(), + mcgrps: mcgrps.as_ptr().cast(), + n_mcgrps, + ..pin_init::zeroed() + }; + if CStr::from_bytes_with_nul(name).is_err() { + panic!("genl_family name not nul-terminated"); + } + if genl_family.name.len() < name.len() { + panic!("genl_family name too long"); + } + let mut i = 0; + while i < name.len() { + genl_family.name[i] = name[i]; + i += 1; + } + Family { + inner: Opaque::new(genl_family), + } + } + + /// Checks if there are any listeners for the given multicast group. + pub fn has_listeners(&self, group: u32) -> bool { + // SAFETY: The family and init_net pointers are valid. + unsafe { + bindings::genl_has_listeners(self.as_raw(), &raw mut bindings::init_net, group) != 0 + } + } + + /// Returns a raw pointer to the underlying `genl_family` structure. + pub fn as_raw(&self) -> *mut bindings::genl_family { + self.inner.get() + } +} + +/// A generic netlink multicast group. +#[repr(transparent)] +pub struct MulticastGroup { + // No Opaque because fully immutable + group: bindings::genl_multicast_group, +} + +// SAFETY: Pure data so thread safe. +unsafe impl Sync for MulticastGroup {} + +impl MulticastGroup { + /// Creates a new `MulticastGroup` instance. + /// + /// Intended to be used from const context only. Will panic if provided with invalid arguments. + pub const fn const_new(name: &CStr) -> MulticastGroup { + let mut group: bindings::genl_multicast_group = pin_init::zeroed(); + + let name = name.to_bytes_with_nul(); + if group.name.len() < name.len() { + panic!("genl_multicast_group name too long"); + } + let mut i = 0; + while i < name.len() { + group.name[i] = name[i]; + i += 1; + } + + MulticastGroup { group } + } +} + +/// A registration of a generic netlink family. +/// +/// This type represents the registration of a [`Family`]. When an instance of this type is +/// dropped, its respective generic netlink family will be unregistered from the system. +/// +/// # Invariants +/// +/// `self.family` always holds a valid reference to an initialized and registered [`Family`]. +pub struct Registration { + family: &'static Family, +} + +impl Family { + /// Registers the generic netlink family with the kernel. + pub fn register(&'static self) -> Result { + // SAFETY: `self.as_raw()` is a valid pointer to a `genl_family` struct. + // The `genl_family` struct is static, so it will outlive the registration. + to_result(unsafe { bindings::genl_register_family(self.as_raw()) })?; + Ok(Registration { family: self }) + } +} + +impl Drop for Registration { + fn drop(&mut self) { + // SAFETY: `self.family.as_raw()` is a valid pointer to a registered `genl_family` struct. + // The `Registration` struct ensures that `genl_unregister_family` is called exactly once + // for this family when it goes out of scope. + unsafe { bindings::genl_unregister_family(self.family.as_raw()) }; + } +} + +#[macros::kunit_tests(rust_netlink)] +mod tests { + use super::*; + + #[test] + fn test_family_flags_bitfield() { + for netnsok in [false, true] { + for parallel_ops in [false, true] { + let mut b_fam = bindings::genl_family { + ..Default::default() + }; + b_fam.set_netnsok(if netnsok { 1 } else { 0 }); + b_fam.set_parallel_ops(if parallel_ops { 1 } else { 0 }); + + let c_bitfield = FamilyFlags { + netnsok, + parallel_ops, + } + .into_bitfield(); + + // SAFETY: The bit field is stored as u8. + let b_val: u8 = unsafe { core::mem::transmute(b_fam._bitfield_1) }; + // SAFETY: The bit field is stored as u8. + let c_val: u8 = unsafe { core::mem::transmute(c_bitfield) }; + assert_eq!(b_val, c_val); + } + } + } +} From f14e0c8183bc73c5ca0ad93670b5b74c71d86df2 Mon Sep 17 00:00:00 2001 From: Carlos Llamas Date: Tue, 7 Jul 2026 10:06:51 +0000 Subject: [PATCH 079/135] rust_binder: report netlink transactions The Android Binder driver supports a netlink API that reports transaction *failures* to a userspace daemon. This allows devices to monitor processes with many failed transactions so that it can e.g. kill misbehaving apps. One very important thing that this monitors is when many oneway messages are sent to a frozen process, so there is special handling to ensure this scenario is surfaced over netlink. Signed-off-by: Carlos Llamas Acked-by: Carlos Llamas Co-developed-by: Alice Ryhl Signed-off-by: Alice Ryhl Link: https://patch.msgid.link/20260707-binder-netlink-v7-3-42b40e4b1ac8@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/Kconfig | 2 +- drivers/android/binder/netlink.rs | 117 +++++++++++++++++++++ drivers/android/binder/rust_binder_main.rs | 8 +- drivers/android/binder/thread.rs | 11 +- drivers/android/binder/transaction.rs | 40 +++++++ rust/uapi/uapi_helper.h | 1 + 6 files changed, 175 insertions(+), 4 deletions(-) create mode 100644 drivers/android/binder/netlink.rs diff --git a/drivers/android/Kconfig b/drivers/android/Kconfig index e2e402c9d175..606a9d07f774 100644 --- a/drivers/android/Kconfig +++ b/drivers/android/Kconfig @@ -16,7 +16,7 @@ config ANDROID_BINDER_IPC config ANDROID_BINDER_IPC_RUST bool "Rust version of Android Binder IPC Driver" - depends on RUST && MMU && !ANDROID_BINDER_IPC + depends on RUST && MMU && NET && !ANDROID_BINDER_IPC help This enables the Rust implementation of the Binder driver. diff --git a/drivers/android/binder/netlink.rs b/drivers/android/binder/netlink.rs new file mode 100644 index 000000000000..beb7ea2edaff --- /dev/null +++ b/drivers/android/binder/netlink.rs @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) +/* Based on: Documentation/netlink/specs/binder.yaml */ + +#![allow(unreachable_pub, clippy::wrong_self_convention)] +use kernel::{ + net::netlink::{ + Family, + GenlMsg, + MulticastGroup, + NetlinkSkBuff, // + }, + prelude::*, // +}; + +pub static BINDER_NL_FAMILY: Family = Family::const_new( + &crate::THIS_MODULE, + kernel::uapi::BINDER_FAMILY_NAME, + kernel::uapi::BINDER_FAMILY_VERSION, + &BINDER_NL_FAMILY_MCGRPS, +); + +static BINDER_NL_FAMILY_MCGRPS: [MulticastGroup; 1] = [MulticastGroup::const_new(c"report")]; + +/// A multicast event sent to userspace subscribers to notify them about +/// binder transaction failures. The generated report provides the full +/// details of the specific transaction that failed. The intention is for +/// programs to monitor these events and react to the failures as needed. +pub struct Report { + skb: GenlMsg, +} + +impl Report { + /// Create a new multicast message. + pub fn new( + size: usize, + portid: u32, + seq: u32, + flags: kernel::alloc::Flags, + ) -> Result { + const BINDER_CMD_REPORT: u8 = kernel::uapi::BINDER_CMD_REPORT as u8; + let skb = NetlinkSkBuff::new(size, flags)?; + let skb = skb.genlmsg_put(portid, seq, &BINDER_NL_FAMILY, BINDER_CMD_REPORT)?; + Ok(Self { skb }) + } + + /// Broadcast this message. + pub fn multicast(self, portid: u32, flags: kernel::alloc::Flags) -> Result { + self.skb.multicast(&BINDER_NL_FAMILY, portid, 0, flags) + } + + /// Check if this message type has listeners. + pub fn has_listeners() -> bool { + BINDER_NL_FAMILY.has_listeners(0) + } + + /// The enum binder_driver_return_protocol returned to the sender. + pub fn error(&mut self, val: u32) -> Result { + const BINDER_A_REPORT_ERROR: c_int = kernel::uapi::BINDER_A_REPORT_ERROR as c_int; + self.skb.put_u32(BINDER_A_REPORT_ERROR, val) + } + + /// The binder context where the transaction occurred. + pub fn context(&mut self, val: &CStr) -> Result { + const BINDER_A_REPORT_CONTEXT: c_int = kernel::uapi::BINDER_A_REPORT_CONTEXT as c_int; + self.skb.put_string(BINDER_A_REPORT_CONTEXT, val) + } + + /// The PID of the sender process. + pub fn from_pid(&mut self, val: u32) -> Result { + const BINDER_A_REPORT_FROM_PID: c_int = kernel::uapi::BINDER_A_REPORT_FROM_PID as c_int; + self.skb.put_u32(BINDER_A_REPORT_FROM_PID, val) + } + + /// The TID of the sender thread. + pub fn from_tid(&mut self, val: u32) -> Result { + const BINDER_A_REPORT_FROM_TID: c_int = kernel::uapi::BINDER_A_REPORT_FROM_TID as c_int; + self.skb.put_u32(BINDER_A_REPORT_FROM_TID, val) + } + + /// The PID of the recipient process. This attribute may not be present + /// if the target could not be determined. + pub fn to_pid(&mut self, val: u32) -> Result { + const BINDER_A_REPORT_TO_PID: c_int = kernel::uapi::BINDER_A_REPORT_TO_PID as c_int; + self.skb.put_u32(BINDER_A_REPORT_TO_PID, val) + } + + /// The TID of the recipient thread. This attribute may not be present + /// if the target could not be determined. + pub fn to_tid(&mut self, val: u32) -> Result { + const BINDER_A_REPORT_TO_TID: c_int = kernel::uapi::BINDER_A_REPORT_TO_TID as c_int; + self.skb.put_u32(BINDER_A_REPORT_TO_TID, val) + } + + /// When present, indicates the failed transaction is a reply. + pub fn is_reply(&mut self) -> Result { + const BINDER_A_REPORT_IS_REPLY: c_int = kernel::uapi::BINDER_A_REPORT_IS_REPLY as c_int; + self.skb.put_flag(BINDER_A_REPORT_IS_REPLY) + } + + /// The bitmask of enum transaction_flags from the transaction. + pub fn flags(&mut self, val: u32) -> Result { + const BINDER_A_REPORT_FLAGS: c_int = kernel::uapi::BINDER_A_REPORT_FLAGS as c_int; + self.skb.put_u32(BINDER_A_REPORT_FLAGS, val) + } + + /// The application-defined code from the transaction. + pub fn code(&mut self, val: u32) -> Result { + const BINDER_A_REPORT_CODE: c_int = kernel::uapi::BINDER_A_REPORT_CODE as c_int; + self.skb.put_u32(BINDER_A_REPORT_CODE, val) + } + + /// The transaction payload size in bytes. + pub fn data_size(&mut self, val: u32) -> Result { + const BINDER_A_REPORT_DATA_SIZE: c_int = kernel::uapi::BINDER_A_REPORT_DATA_SIZE as c_int; + self.skb.put_u32(BINDER_A_REPORT_DATA_SIZE, val) + } +} diff --git a/drivers/android/binder/rust_binder_main.rs b/drivers/android/binder/rust_binder_main.rs index 15c7b65928d8..9e6cda960722 100644 --- a/drivers/android/binder/rust_binder_main.rs +++ b/drivers/android/binder/rust_binder_main.rs @@ -34,6 +34,7 @@ mod defs; #[macro_use] mod debug; mod error; +mod netlink; mod node; mod page_range; mod process; @@ -294,19 +295,22 @@ fn ptr_align(value: usize) -> Option { // SAFETY: We call register in `init`. static BINDER_SHRINKER: Shrinker = unsafe { Shrinker::new() }; -struct BinderModule {} +struct BinderModule { + _netlink: kernel::net::netlink::Registration, +} impl kernel::Module for BinderModule { fn init(_module: &'static kernel::ThisModule) -> Result { // SAFETY: The module initializer never runs twice, so we only call this once. unsafe { crate::context::CONTEXTS.init() }; + let netlink = crate::netlink::BINDER_NL_FAMILY.register()?; BINDER_SHRINKER.register(c"android-binder")?; // SAFETY: The module is being loaded, so we can initialize binderfs. unsafe { kernel::error::to_result(binderfs::init_rust_binderfs())? }; - Ok(Self {}) + Ok(Self { _netlink: netlink }) } } diff --git a/drivers/android/binder/thread.rs b/drivers/android/binder/thread.rs index edc2613d13b5..a7a190e1b000 100644 --- a/drivers/android/binder/thread.rs +++ b/drivers/android/binder/thread.rs @@ -1301,6 +1301,15 @@ impl Thread { } } + if info.oneway_spam_suspect { + // If this is both a oneway spam suspect and a failure, we report it twice. This is + // useful in case the transaction failed with BR_TRANSACTION_PENDING_FROZEN. + info.report_netlink(BR_ONEWAY_SPAM_SUSPECT, &self.process.ctx); + } + if info.reply != 0 { + info.report_netlink(info.reply, &self.process.ctx); + } + Ok(()) } @@ -1387,11 +1396,11 @@ impl Thread { info.from_tid, info.to_pid ); - let param = err.source.as_ref().map_or(0, |e| e.to_errno()); let ee = ExtendedError::new(info.debug_id as u32, err.reply, param); orig.from .deliver_reply(Err(BR_FAILED_REPLY), &orig, Some(ee)); + info.reply = BR_FAILED_REPLY; err.reply = BR_TRANSACTION_COMPLETE; err }); diff --git a/drivers/android/binder/transaction.rs b/drivers/android/binder/transaction.rs index 0528070fe700..96d45c6816fe 100644 --- a/drivers/android/binder/transaction.rs +++ b/drivers/android/binder/transaction.rs @@ -3,6 +3,7 @@ // Copyright (C) 2025 Google LLC. use kernel::{ + net::netlink::GENLMSG_DEFAULT_SIZE, prelude::*, seq_file::SeqFile, seq_print, @@ -18,6 +19,7 @@ use crate::{ allocation::{Allocation, TranslatedFds}, defs::*, error::{BinderError, BinderResult}, + netlink::Report, node::{Node, NodeRef}, process::{Process, ProcessInner}, ptr_align, @@ -51,6 +53,44 @@ impl TransactionInfo { pub(crate) fn is_oneway(&self) -> bool { self.flags & TF_ONE_WAY != 0 } + + pub(crate) fn report_netlink(&self, reply: u32, ctx: &crate::Context) { + if let Err(err) = self.report_netlink_inner(reply, ctx) { + pr_warn!( + "{}:{} netlink report failed: {err:?}\n", + self.from_pid, + self.from_tid + ); + } + } + + fn report_netlink_inner(&self, reply: u32, ctx: &crate::Context) -> kernel::error::Result { + if !Report::has_listeners() { + return Ok(()); + } + let mut report = Report::new(GENLMSG_DEFAULT_SIZE, 0, 0, GFP_KERNEL)?; + + report.error(reply)?; + report.context(&ctx.name)?; + report.from_pid(self.from_pid as u32)?; + report.from_tid(self.from_tid as u32)?; + if self.to_pid != 0 { + report.to_pid(self.to_pid as u32)?; + } + if self.to_tid != 0 { + report.to_tid(self.to_tid as u32)?; + } + + if self.is_reply { + report.is_reply()?; + } + report.flags(self.flags)?; + report.code(self.code)?; + report.data_size(self.data_size as u32)?; + + report.multicast(0, GFP_KERNEL)?; + Ok(()) + } } use core::mem::offset_of; diff --git a/rust/uapi/uapi_helper.h b/rust/uapi/uapi_helper.h index 06d7d1a2e8da..86c7b6b284b0 100644 --- a/rust/uapi/uapi_helper.h +++ b/rust/uapi/uapi_helper.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include From e5e86df8b666152bc99fab4be0906b92a271964d Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Tue, 7 Jul 2026 10:43:12 +0000 Subject: [PATCH 080/135] rust: poll: use kfree_rcu() for PollCondVar Rust Binder currently uses PollCondVar, but it calls synchronize_rcu() in the destructor, which we would like to avoid. Add a variation of PollCondVar that kfree_rcu() instead. One could avoid the `rcu` field and allocate the rcu_head on drop using a fallback to synchronize_rcu() on ENOMEM. However, I'd prefer to avoid the potential for synchronize_rcu(), and Binder will only use this for a small fraction of processes, so even if it changes which kmalloc bucket it falls into, the extra memory is not a problem. Signed-off-by: Alice Ryhl Reviewed-by: Boqun Feng Link: https://patch.msgid.link/20260707-upgrade-poll-v6-1-4b8fae7bf1d9@google.com Signed-off-by: Greg Kroah-Hartman --- rust/kernel/sync/poll.rs | 73 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/rust/kernel/sync/poll.rs b/rust/kernel/sync/poll.rs index 0ec985d560c8..684dfa242b1a 100644 --- a/rust/kernel/sync/poll.rs +++ b/rust/kernel/sync/poll.rs @@ -5,12 +5,18 @@ //! Utilities for working with `struct poll_table`. use crate::{ + alloc::AllocError, bindings, fs::File, prelude::*, sync::{CondVar, LockClassKey}, + types::Opaque, // +}; +use core::{ + marker::PhantomData, + mem::ManuallyDrop, + ops::Deref, // }; -use core::{marker::PhantomData, ops::Deref}; /// Creates a [`PollCondVar`] initialiser with the given name and a newly-created lock class. #[macro_export] @@ -66,6 +72,7 @@ impl<'a> PollTable<'a> { /// /// [`CondVar`]: crate::sync::CondVar #[pin_data(PinnedDrop)] +#[repr(transparent)] pub struct PollCondVar { #[pin] inner: CondVar, @@ -104,3 +111,67 @@ impl PinnedDrop for PollCondVar { unsafe { bindings::synchronize_rcu() }; } } + +/// A [`KBox`] that uses `kfree_rcu`. +/// +/// [`KBox`]: PollCondVar +pub struct PollCondVarBox { + inner: ManuallyDrop>>, +} + +#[pin_data] +#[repr(C)] +struct PollCondVarBoxInner { + #[pin] + inner: PollCondVar, + rcu: Opaque, +} + +// SAFETY: PollCondVar is Send +unsafe impl Send for PollCondVarBoxInner {} +// SAFETY: PollCondVar is Sync +unsafe impl Sync for PollCondVarBoxInner {} + +impl PollCondVarBox { + /// Constructs a new boxed [`PollCondVar`]. + pub fn new(name: &'static CStr, key: Pin<&'static LockClassKey>) -> Result { + let b = KBox::pin_init( + pin_init!(PollCondVarBoxInner { + inner <- PollCondVar::new(name, key), + rcu: Opaque::uninit(), + }), + GFP_KERNEL, + ) + .map_err(|_| AllocError)?; + + Ok(PollCondVarBox { + inner: ManuallyDrop::new(b), + }) + } +} + +impl Deref for PollCondVarBox { + type Target = PollCondVar; + fn deref(&self) -> &PollCondVar { + &self.inner.inner + } +} + +impl Drop for PollCondVarBox { + #[inline] + fn drop(&mut self) { + // SAFETY: ManuallyDrop::take ok because not already taken. + let boxed = unsafe { ManuallyDrop::take(&mut self.inner) }; + + // SAFETY: The code below frees the box without calling the actual destructor of the type, + // but it's okay because it re-implements the destructor using `kfree_rcu()` in place of + // `synchronize_rcu()`. + let ptr = KBox::into_raw(unsafe { Pin::into_inner_unchecked(boxed) }); + + // SAFETY: The pointer points at a valid `wait_queue_head`. + unsafe { bindings::__wake_up_pollfree((*ptr).inner.inner.wait_queue_head.get()) }; + + // SAFETY: This was allocated using `KBox::pin_init`, so it can be freed with `kvfree`. + unsafe { bindings::kvfree_call_rcu((*ptr).rcu.get(), ptr.cast::()) }; + } +} From dbb17c9ea7567c6ecefe47104cfcc255b91e4089 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Tue, 7 Jul 2026 10:43:13 +0000 Subject: [PATCH 081/135] rust_binder: move (e)poll wait queue to Process Most processes do not use Rust Binder with epoll, so avoid paying the synchronize_rcu() cost in drop for those that don't need it. For those that do, we also manage to replace synchronize_rcu() with kfree_rcu(), though we introduce an extra allocation. In case the last ref to an Arc is dropped outside of deferred_release(), this also ensures that synchronize_rcu() is not called in destructor of Arc in other places. Theoretically that could lead to jank by making other syscalls slow, which would be problematic. Signed-off-by: Alice Ryhl Reviewed-by: Boqun Feng Link: https://patch.msgid.link/20260707-upgrade-poll-v6-2-4b8fae7bf1d9@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/node.rs | 4 +- drivers/android/binder/process.rs | 67 ++++++++++++++++------- drivers/android/binder/thread.rs | 78 +++++++++++++-------------- drivers/android/binder/transaction.rs | 6 ++- 4 files changed, 95 insertions(+), 60 deletions(-) diff --git a/drivers/android/binder/node.rs b/drivers/android/binder/node.rs index c73cdf82100f..b74ef32b0d94 100644 --- a/drivers/android/binder/node.rs +++ b/drivers/android/binder/node.rs @@ -538,7 +538,7 @@ impl Node { inner.oneway_todo.push_back(transaction); } else { inner.has_oneway_transaction = true; - guard.push_work(transaction)?; + guard.push_work(&self.owner, transaction)?; } Ok(()) } @@ -570,7 +570,7 @@ impl Node { let transaction = inner.oneway_todo.pop_front(); inner.has_oneway_transaction = transaction.is_some(); if let Some(transaction) = transaction { - match guard.push_work(transaction) { + match guard.push_work(&self.owner, transaction) { Ok(()) => {} Err((_err, work)) => { // Process is dead. diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs index eb2f08bec655..962f38453252 100644 --- a/drivers/android/binder/process.rs +++ b/drivers/android/binder/process.rs @@ -30,7 +30,8 @@ use kernel::{ sync::{ aref::ARef, lock::{spinlock::SpinLockBackend, Guard}, - Arc, ArcBorrow, CondVar, CondVarTimeoutResult, SpinLock, UniqueArc, + poll::PollCondVarBox, + Arc, ArcBorrow, CondVar, CondVarTimeoutResult, SetOnce, SpinLock, UniqueArc, }, task::{Pid, Task}, uaccess::{UserSlice, UserSliceReader}, @@ -172,21 +173,26 @@ impl ProcessInner { /// taken while holding the inner process lock. pub(crate) fn push_work( &mut self, + proc: &Process, work: DLArc, ) -> Result<(), (BinderError, DLArc)> { + let sync = work.should_sync_wakeup(); + // Try to find a ready thread to which to push the work. if let Some(thread) = self.ready_threads.pop_front() { // Push to thread while holding state lock. This prevents the thread from giving up // (for example, because of a signal) when we're about to deliver work. - match thread.push_work(work) { + match thread.push_work_inner(work, sync) { PushWorkRes::Ok => Ok(()), + PushWorkRes::OkNotifyPoll => { + proc.notify_poll(sync); + Ok(()) + } PushWorkRes::FailedDead(work) => Err((BinderError::new_dead(), work)), } } else if self.is_dead { Err((BinderError::new_dead(), work)) } else { - let sync = work.should_sync_wakeup(); - // Didn't find a thread waiting for proc work; this can happen // in two scenarios: // 1. All threads are busy handling transactions @@ -194,17 +200,12 @@ impl ProcessInner { // the kernel driver soon and pick up this work. // 2. Threads are using the (e)poll interface, in which case // they may be blocked on the waitqueue without having been - // added to waiting_threads. For this case, we just iterate - // over all threads not handling transaction work, and - // wake them all up. We wake all because we don't know whether - // a thread that called into (e)poll is handling non-binder - // work currently. + // added to waiting_threads. For this case, we wake it up + // directly. self.work.push_back(work); // Wake up polling threads, if any. - for thread in self.threads.values() { - thread.notify_if_poll_ready(sync); - } + proc.notify_poll(sync); Ok(()) } @@ -227,11 +228,11 @@ impl ProcessInner { // If we decided that we need to push work, push either to the process or to a thread if // one is specified. - if let Some(node) = push { + if let Some(pnode) = push { if let Some(thread) = othread { - thread.push_work_deferred(node); + thread.push_work_deferred(pnode); } else { - let _ = self.push_work(node); + let _ = self.push_work(&node.owner, pnode); // Nothing to do: `push_work` may fail if the process is dead, but that's ok as in // that case, it doesn't care about the notification. } @@ -457,6 +458,12 @@ pub(crate) struct Process { #[pin] node_refs: SpinLock, + // Synchronizes `register_wait` calls to the `PollCondVarBox`. + // + // The `PollCondVarBox` is not stored here because synchronization is + // done for `register_wait` only. Wakeups do not take this lock. + poll: SetOnce, + // Work node for deferred work item. #[pin] defer_work: Work, @@ -516,6 +523,7 @@ impl Process { defer_work <- kernel::new_work!("Process::defer_work"), links <- ListLinks::new(), stats: BinderStats::new(), + poll: SetOnce::new(), }), GFP_KERNEL, )?; @@ -715,7 +723,7 @@ impl Process { pub(crate) fn push_work(&self, work: DLArc) -> BinderResult { // If push_work fails, drop the work item outside the lock. - let res = self.inner.lock().push_work(work); + let res = self.inner.lock().push_work(self, work); match res { Ok(()) => Ok(()), Err((err, work)) => { @@ -1024,7 +1032,7 @@ impl Process { if let Ok(Some(node)) = inner.get_existing_node(ptr, cookie) { if let Some(node) = node.inc_ref_done_locked(strong, &mut inner) { // This only fails if the process is dead. - let _ = inner.push_work(node); + let _ = inner.push_work(self, node); } } Ok(()) @@ -1573,6 +1581,15 @@ impl Process { } } } + + pub(crate) fn notify_poll(&self, sync: bool) { + if let Some(poll) = self.poll.as_ref() { + if sync { + poll.notify_sync(); + } + poll.notify_all(); + } + } } fn get_frozen_status(data: UserSlice) -> Result { @@ -1766,7 +1783,21 @@ impl Process { table: PollTable<'_>, ) -> Result { let thread = this.get_current_thread()?; - let (from_proc, mut mask) = thread.poll(file, table); + { + let poll = loop { + if let Some(poll) = this.poll.as_ref() { + break poll; + } + + let poll = PollCondVarBox::new(c"Process::poll", kernel::static_lock_class!())?; + // Reuse our existing lock to synchronize callers initializing. + let _guard = this.node_refs.lock(); + this.poll.populate(poll); + }; + + table.register_wait(file, poll); + } + let (from_proc, mut mask) = thread.poll()?; if mask == 0 && from_proc && !this.inner.lock().work.is_empty() { mask |= bindings::POLLIN; } diff --git a/drivers/android/binder/thread.rs b/drivers/android/binder/thread.rs index a7a190e1b000..a51821dde0ad 100644 --- a/drivers/android/binder/thread.rs +++ b/drivers/android/binder/thread.rs @@ -9,15 +9,14 @@ use kernel::{ bindings, - fs::{File, LocalFile}, + fs::LocalFile, list::{AtomicTracker, List, ListArc, ListLinks, TryNewListArc}, prelude::*, security, seq_file::SeqFile, seq_print, sync::atomic::{ordering::Relaxed, Atomic}, - sync::poll::{PollCondVar, PollTable}, - sync::{aref::ARef, Arc, SpinLock}, + sync::{aref::ARef, Arc, CondVar, SpinLock}, task::Task, uaccess::{UserPtr, UserSlice, UserSliceReader}, uapi, @@ -225,8 +224,10 @@ impl UnusedBufferSpace { } } +#[must_use] pub(crate) enum PushWorkRes { Ok, + OkNotifyPoll, FailedDead(DLArc), } @@ -234,6 +235,7 @@ impl PushWorkRes { fn is_ok(&self) -> bool { match self { PushWorkRes::Ok => true, + PushWorkRes::OkNotifyPoll => true, PushWorkRes::FailedDead(_) => false, } } @@ -310,27 +312,32 @@ impl InnerThread { fn push_work(&mut self, work: DLArc) -> PushWorkRes { if self.is_dead { - PushWorkRes::FailedDead(work) + return PushWorkRes::FailedDead(work); + } + self.work_list.push_back(work); + self.process_work_list = true; + if self.looper_flags & LOOPER_POLL != 0 { + PushWorkRes::OkNotifyPoll } else { - self.work_list.push_back(work); - self.process_work_list = true; PushWorkRes::Ok } } - fn push_reply_work(&mut self, code: u32) { + fn push_reply_work(&mut self, code: u32) -> PushWorkRes { if let Ok(work) = ListArc::try_from_arc(self.reply_work.clone()) { work.set_error_code(code); - self.push_work(work); + self.push_work(work) } else { pr_warn!("Thread reply work is already in use."); + PushWorkRes::Ok } } fn push_return_work(&mut self, reply: u32) { if let Ok(work) = ListArc::try_from_arc(self.return_work.clone()) { work.set_error_code(reply); - self.push_work(work); + // Not notifying: Reply to current thread. + let _ = self.push_work(work); } else { pr_warn!("Thread return work is already in use."); } @@ -422,7 +429,7 @@ pub(crate) struct Thread { #[pin] inner: SpinLock, #[pin] - work_condvar: PollCondVar, + work_condvar: CondVar, /// Used to insert this thread into the process' `ready_threads` list. /// /// INVARIANT: May never be used for any other list than the `self.process.ready_threads`. @@ -453,7 +460,7 @@ impl Thread { process, task: ARef::from(&**kernel::current!()), inner <- kernel::new_spinlock!(inner, "Thread::inner"), - work_condvar <- kernel::new_poll_condvar!("Thread::work_condvar"), + work_condvar <- kernel::new_condvar!("Thread::work_condvar"), links <- ListLinks::new(), links_track <- AtomicTracker::new(), }), @@ -624,7 +631,14 @@ impl Thread { /// Returns whether the item was successfully pushed. This can only fail if the thread is dead. pub(crate) fn push_work(&self, work: DLArc) -> PushWorkRes { let sync = work.should_sync_wakeup(); + self.push_work_inner(work, sync) + } + pub(crate) fn push_work_inner( + &self, + work: DLArc, + sync: bool, + ) -> PushWorkRes { let res = self.inner.lock().push_work(work); if res.is_ok() { @@ -643,7 +657,8 @@ impl Thread { pub(crate) fn push_work_if_looper(&self, work: DLArc) -> BinderResult { let mut inner = self.inner.lock(); if inner.is_looper() && !inner.is_dead { - inner.push_work(work); + // Not notifying: Reply to current thread. + let _ = inner.push_work(work); Ok(()) } else { drop(inner); @@ -1160,7 +1175,7 @@ impl Thread { transaction.set_outstanding(&mut self.process.inner.lock()); } - { + let ret = { let mut inner = self.inner.lock(); if !inner.pop_transaction_replied(transaction) { return false; @@ -1177,15 +1192,16 @@ impl Thread { } match reply { - Ok(work) => { - inner.push_work(work); - } + Ok(work) => inner.push_work(work), Err(code) => inner.push_reply_work(code), } - } + }; // Notify the thread now that we've released the inner lock. self.work_condvar.notify_sync(); + if matches!(ret, PushWorkRes::OkNotifyPoll) { + self.process.notify_poll(true); + } false } @@ -1382,7 +1398,8 @@ impl Thread { let process = orig.from.process.clone(); let allow_fds = orig.flags & TF_ACCEPT_FDS != 0; let reply = Transaction::new_reply(self, process, info, allow_fds)?; - self.inner.lock().push_work(completion); + // Not notifying: Reply to current thread. + let _ = self.inner.lock().push_work(completion); orig.from.deliver_reply(Ok(reply), &orig, None); Ok(()) })() @@ -1421,7 +1438,8 @@ impl Thread { let list_completion = DTRWrap::arc_try_new(DeliverCode::new(code, self.process.task.pid()))?; let completion = list_completion.clone_arc(); - self.inner.lock().push_work(list_completion); + // Not notifying: Reply to current thread. + let _ = self.inner.lock().push_work(list_completion); match transaction.submit(info) { Ok(()) => Ok(()), Err(err) => { @@ -1623,10 +1641,9 @@ impl Thread { ret } - pub(crate) fn poll(&self, file: &File, table: PollTable<'_>) -> (bool, u32) { - table.register_wait(file, &self.work_condvar); + pub(crate) fn poll(&self) -> Result<(bool, u32)> { let mut inner = self.inner.lock(); - (inner.should_use_process_work_queue(), inner.poll()) + Ok((inner.should_use_process_work_queue(), inner.poll())) } /// Make the call to `get_work` or `get_work_local` return immediately, if any. @@ -1643,26 +1660,9 @@ impl Thread { } } - pub(crate) fn notify_if_poll_ready(&self, sync: bool) { - // Determine if we need to notify. This requires the lock. - let inner = self.inner.lock(); - let notify = inner.looper_flags & LOOPER_POLL != 0 && inner.should_use_process_work_queue(); - drop(inner); - - // Now that the lock is no longer held, notify the waiters if we have to. - if notify { - if sync { - self.work_condvar.notify_sync(); - } else { - self.work_condvar.notify_one(); - } - } - } - pub(crate) fn release(self: &Arc) { self.inner.lock().is_dead = true; - //self.work_condvar.clear(); self.unwind_transaction_stack(); // Cancel all pending work items. diff --git a/drivers/android/binder/transaction.rs b/drivers/android/binder/transaction.rs index 96d45c6816fe..13dfb5c5c955 100644 --- a/drivers/android/binder/transaction.rs +++ b/drivers/android/binder/transaction.rs @@ -371,11 +371,15 @@ impl Transaction { crate::trace::trace_transaction(false, &self, Some(&thread.task)); match thread.push_work(self) { PushWorkRes::Ok => Ok(()), + PushWorkRes::OkNotifyPoll => { + process.notify_poll(true); + Ok(()) + } PushWorkRes::FailedDead(me) => Err((BinderError::new_dead(), me)), } } else { crate::trace::trace_transaction(false, &self, None); - process_inner.push_work(self) + process_inner.push_work(&process, self) }; drop(process_inner); From 0f7f34c67ead630bf485c805a95fc540d80f2c7f Mon Sep 17 00:00:00 2001 From: Jahnavi MN Date: Thu, 16 Jul 2026 13:02:34 +0000 Subject: [PATCH 082/135] rust_binder: Update defer_work bitmaps to use kernel::impl_flags! - Define `DeferWorks(u8)` and `DeferWork` enum using `bit_u8` offsets. - Change `ProcessInner.defer_work` type from `u8` to `DeferWorks`. - Update `Process::release()` and `Process::flush()` to check for empty states using `DeferWorks::empty()`. - Update the workqueue runner to inspect flags using `.contains()`. Signed-off-by: Jahnavi MN Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20260716-b4-rust_binder_impl_flags-v1-1-b4201d3f15b3@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/process.rs | 34 ++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs index 962f38453252..1778628d8acd 100644 --- a/drivers/android/binder/process.rs +++ b/drivers/android/binder/process.rs @@ -16,6 +16,7 @@ use core::mem::take; use kernel::{ bindings, + bits::bit_u8, cred::Credential, error::Error, fs::file::{self, File}, @@ -71,9 +72,18 @@ impl Mapping { } } -// bitflags for defer_work. -const PROC_DEFER_FLUSH: u8 = 1; -const PROC_DEFER_RELEASE: u8 = 2; +kernel::impl_flags!( + /// Represents multiple deferred work flags. + #[derive(Debug, Clone, Default, Copy, PartialEq, Eq)] + pub struct DeferWorks(u8); + + /// Represents a single deferred work category. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum DeferWork { + Flush = bit_u8(0), + Release = bit_u8(1), + } +); #[derive(Copy, Clone)] pub(crate) enum IsFrozen { @@ -122,7 +132,7 @@ pub(crate) struct ProcessInner { started_thread_count: u32, /// Bitmap of deferred work to do. - defer_work: u8, + defer_work: DeferWorks, /// Number of transactions to be transmitted before processes in freeze_wait /// are woken up. @@ -152,7 +162,7 @@ impl ProcessInner { requested_thread_count: 0, max_threads: 0, started_thread_count: 0, - defer_work: 0, + defer_work: DeferWorks::default(), outstanding_txns: 0, is_frozen: IsFrozen::No, sync_recv: false, @@ -496,13 +506,13 @@ impl workqueue::WorkItem for Process { { let mut inner = me.inner.lock(); defer = inner.defer_work; - inner.defer_work = 0; + inner.defer_work = DeferWorks::default(); } - if defer & PROC_DEFER_FLUSH != 0 { + if defer.contains(DeferWork::Flush) { me.deferred_flush(); } - if defer & PROC_DEFER_RELEASE != 0 { + if defer.contains(DeferWork::Release) { me.deferred_release(); } } @@ -1706,8 +1716,8 @@ impl Process { let should_schedule; { let mut inner = this.inner.lock(); - should_schedule = inner.defer_work == 0; - inner.defer_work |= PROC_DEFER_RELEASE; + should_schedule = inner.defer_work == DeferWorks::empty(); + inner.defer_work |= DeferWork::Release; binderfs_file = inner.binderfs_file.take(); } @@ -1724,8 +1734,8 @@ impl Process { let should_schedule; { let mut inner = this.inner.lock(); - should_schedule = inner.defer_work == 0; - inner.defer_work |= PROC_DEFER_FLUSH; + should_schedule = inner.defer_work == DeferWorks::empty(); + inner.defer_work |= DeferWork::Flush; } if should_schedule { From 4b17dfb3e22fdccf74839d2fc52362ddc257024e Mon Sep 17 00:00:00 2001 From: Yilin Chen <1479826151@qq.com> Date: Tue, 7 Jul 2026 16:10:30 +0000 Subject: [PATCH 083/135] rust: miscdevice: fix write_iter safety docs The write_iter callback consumes data from the supplied iov_iter and wraps it as an IovIterSource. Its Safety docs required a valid iov_iter for writing, but the implementation and the IovIterSource contract require one that is valid for reading. Update the docs to match that direction. Assisted-by: Codex:GPT-5 Signed-off-by: Yilin Chen <1479826151@qq.com> Link: https://patch.msgid.link/tencent_8CD671E0F35223030143524D045F3BCAD506@qq.com Signed-off-by: Greg Kroah-Hartman --- rust/kernel/miscdevice.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/kernel/miscdevice.rs b/rust/kernel/miscdevice.rs index 83ce50def5ac..3abef1b8543d 100644 --- a/rust/kernel/miscdevice.rs +++ b/rust/kernel/miscdevice.rs @@ -289,7 +289,7 @@ impl MiscdeviceVTable { /// # Safety /// /// `kiocb` must be correspond to a valid file that is associated with a - /// `MiscDeviceRegistration`. `iter` must be a valid `struct iov_iter` for writing. + /// `MiscDeviceRegistration`. `iter` must be a valid `struct iov_iter` for reading. unsafe extern "C" fn write_iter( kiocb: *mut bindings::kiocb, iter: *mut bindings::iov_iter, From 5d577fa6feaf2ef02751fc4a89fc9a695aa0f6b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Antinori?= Date: Thu, 2 Jul 2026 17:58:00 -0300 Subject: [PATCH 084/135] rust_binder: use pin_init::zeroed for file_operations initialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All types in `bindings` implement `Zeroable` if they can. This enables using `pin_init::zeroed()` for `file_operations` initialization instead of relying on `unsafe { core::mem::MaybeUninit::zeroed().assume_init() }`. This change improves readability and removes an unnecessary unsafe block. Link: https://github.com/Rust-for-Linux/linux/issues/1189 Suggested-by: Benno Lossin Signed-off-by: Nicolás Antinori Link: https://patch.msgid.link/20260702205803.552476-1-nico.antinori.7@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/rust_binder_main.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/android/binder/rust_binder_main.rs b/drivers/android/binder/rust_binder_main.rs index 9e6cda960722..e6805bc06f43 100644 --- a/drivers/android/binder/rust_binder_main.rs +++ b/drivers/android/binder/rust_binder_main.rs @@ -324,9 +324,6 @@ unsafe impl Sync for AssertSync {} #[no_mangle] #[used] pub static rust_binder_fops: AssertSync = { - // SAFETY: All zeroes is safe for the `file_operations` type. - let zeroed_ops = unsafe { core::mem::MaybeUninit::zeroed().assume_init() }; - let ops = kernel::bindings::file_operations { owner: THIS_MODULE.as_ptr(), poll: Some(rust_binder_poll), @@ -336,7 +333,7 @@ pub static rust_binder_fops: AssertSync = { open: Some(rust_binder_open), release: Some(rust_binder_release), flush: Some(rust_binder_flush), - ..zeroed_ops + ..pin_init::zeroed() }; AssertSync(ops) }; From 65f0ecb9ec85cb3a2e2372a6427dcc100691fc57 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Wed, 13 May 2026 17:18:42 +0300 Subject: [PATCH 085/135] issei: initial driver skeleton The ISSEI (Intel Silicon Security Engine Interface) subsystem provides a communication channel between the host and the Silicon Security Engine. Prepare basic driver functions and character device for user-space communication. Add DMA access routines for ISSEI HECI devices. Add of DMA-related structures and implementation of routines for setting up DMA, as well as reading and writing DMA buffers. Reviewed-by: Karol Wachowski Co-developed-by: Vitaly Lubart Signed-off-by: Vitaly Lubart Signed-off-by: Alexander Usyskin Link: https://patch.msgid.link/20260513-issei-for-upstream-v1-1-f590038678f9@intel.com Signed-off-by: Greg Kroah-Hartman --- Documentation/driver-api/index.rst | 1 + Documentation/driver-api/issei/index.rst | 16 ++ Documentation/driver-api/issei/issei.rst | 135 ++++++++++++++ MAINTAINERS | 7 + drivers/misc/Kconfig | 1 + drivers/misc/Makefile | 1 + drivers/misc/issei/Kconfig | 13 ++ drivers/misc/issei/Makefile | 7 + drivers/misc/issei/cdev.c | 219 +++++++++++++++++++++++ drivers/misc/issei/cdev.h | 16 ++ drivers/misc/issei/dma.c | 154 ++++++++++++++++ drivers/misc/issei/dma.h | 69 +++++++ drivers/misc/issei/hw_msg.h | 163 +++++++++++++++++ drivers/misc/issei/issei_dev.h | 160 +++++++++++++++++ include/uapi/linux/issei.h | 69 +++++++ 15 files changed, 1031 insertions(+) create mode 100644 Documentation/driver-api/issei/index.rst create mode 100644 Documentation/driver-api/issei/issei.rst create mode 100644 drivers/misc/issei/Kconfig create mode 100644 drivers/misc/issei/Makefile create mode 100644 drivers/misc/issei/cdev.c create mode 100644 drivers/misc/issei/cdev.h create mode 100644 drivers/misc/issei/dma.c create mode 100644 drivers/misc/issei/dma.h create mode 100644 drivers/misc/issei/hw_msg.h create mode 100644 drivers/misc/issei/issei_dev.h create mode 100644 include/uapi/linux/issei.h diff --git a/Documentation/driver-api/index.rst b/Documentation/driver-api/index.rst index eaf7161ff957..6601a258690f 100644 --- a/Documentation/driver-api/index.rst +++ b/Documentation/driver-api/index.rst @@ -105,6 +105,7 @@ Subsystem-specific APIs interconnect ipmb ipmi + issei/index libata mailbox md/index diff --git a/Documentation/driver-api/issei/index.rst b/Documentation/driver-api/issei/index.rst new file mode 100644 index 000000000000..604267463fd4 --- /dev/null +++ b/Documentation/driver-api/issei/index.rst @@ -0,0 +1,16 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: + +========================================================= +The Intel Silicon Security Engine Interface (Intel SSEI) +========================================================= + +**Copyright** |copy| 2026 Intel Corporation + + +.. toctree:: + :caption: Table of Contents + :maxdepth: 3 + + issei diff --git a/Documentation/driver-api/issei/issei.rst b/Documentation/driver-api/issei/issei.rst new file mode 100644 index 000000000000..a5e99e92e095 --- /dev/null +++ b/Documentation/driver-api/issei/issei.rst @@ -0,0 +1,135 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Introduction +============ + +The Intel Silicon Security Engine (Intel SSE) is an isolated and +protected computing resource (Co-processor) residing inside +Intel client chipsets released in 2024 (Lunar Lake) or later. +The Intel SSE provide security support and platform boot orchestration. +The actual feature set depends on the Intel chipset SKU. + +The Intel Silicon Security Engine Interface (Intel SSEI) +is the interface between the Host and Intel SSE. +This interface is exposed to the host as one or more PCI devices. +The Intel SSEI Driver is in charge of the communication channel between +a host application and the Intel SSE features. + +Each Intel SSE feature, or Intel SSE Client is addressed by a unique UUID and +each client has its own protocol. The protocol is message-based with a +header and payload up to maximal number of bytes advertised by the client, +upon connection. + +Intel SSEI Driver +================= + +The driver exposes a character device with device nodes /dev/isseiX. + +An application maintains communication with an Intel SSE feature while +/dev/isseiX is open. The binding to a specific feature is performed by calling +:c:macro:`IOCTL_ISSEI_CONNECT_CLIENT`, which passes the desired UUID. +The number of instances of an Intel SSE feature that can be opened +at the same time is limited to single instance. + +The driver is transparent to data that are passed between firmware feature +and host application. + +Because some of the Intel SSE features can change the system +configuration, the driver by default allows only a privileged +user to access it. + +The connection termination is performed by calling +:c:macro:`IOCTL_ISSEI_DISCONNECT_CLIENT`. + +The session is terminated calling :c:expr:`close(fd)`. + +A code snippet for an application communicating with SPDM client: + +.. code-block:: C + + struct issei_connect_client_data data = {.in_client_uuid = + {0xe8, 0x51, 0x49, 0xdf, 0x94, 0x47, 0x4C, + 0x9A, 0x83, 0x67, 0xC4, 0xE3, 0x34, 0x64, 0xF1, 0xB4}}; + __u8 req_data[] = {0x10, 0x84, 0x00, 0x00}; /* SPDM Get Version */ + size_t req_data_len = sizeof(req_data); + __u8 res_data[256]; + size_t res_data_len = sizeof(res_data); + int fd = open("/dev/issei0", O_RDWR); + + ioctl(fd, IOCTL_ISSEI_CONNECT_CLIENT, &data); + + printf("Ver=%d, MaxLen=%u, Flags=0x%08X\n", + data.out_client_properties.protocol_version, + data.out_client_properties.max_msg_length, + data.out_client_properties.flags); + + [...] + + write(fd, req_data, req_data_len); + + [...] + + read(fd, res_data, res_data_len); + + printf("SPDM version count %u, version[0]=%02X%02X\n", + res_data[5], res_data[6], res_data[7]); + + [...] + + ioctl(fd, IOCTL_ISSEI_DISCONNECT_CLIENT, &data); + + [...] + + close(fd); + + +User space API ioctl +==================== + +The Intel SSEI Driver supports the following ioctl commands: + +IOCTL_ISSEI_CONNECT_CLIENT +-------------------------- +Connect to firmware Feature/Client. + +.. code-block:: none + + Usage: + + struct issei_connect_client_data client_data; + + ioctl(fd, IOCTL_ISSEI_CONNECT_CLIENT, &client_data); + + struct issei_connect_client_data - contain the following + Inputs: + in_client_uuid - UUID of the FW Feature that needs to connect to. + Outputs: + out_client_properties - Client Properties: MTU, Protocol Version and Flags. + + Error returns: + ENOTTY No such client (i.e. wrong UUID) or connection is not allowed. + EINVAL Wrong IOCTL Number + ENODEV Device or Connection is not initialized or ready. + ENOMEM Unable to allocate memory to client internal data. + EFAULT Fatal Error (e.g. Unable to access user input data) + EBUSY Connection Already Open + +:Note: + max_msg_length (MTU) in client properties describes the maximum + data that can be sent or received. (e.g. with MTU=2K, can send + requests up to bytes 2k and received responses up to 2k bytes). + +IOCTL_ISSEI_DISCONNECT_CLIENT +----------------------------- +Disconnect from firmware Feature/Client. + +.. code-block:: none + + Usage: + + ioctl(fd, IOCTL_ISSEI_DISCONNECT_CLIENT, NULL); + + Error returns: + EINVAL Wrong IOCTL Number + ENODEV Device or Connection is not initialized or ready. + ENOTCONN Feature/Client is not connected. diff --git a/MAINTAINERS b/MAINTAINERS index be0af1665945..b6bf86f9f8ab 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13360,6 +13360,13 @@ F: drivers/platform/x86/intel/sdsi.c F: tools/arch/x86/intel_sdsi/ F: tools/testing/selftests/drivers/sdsi/ +INTEL SILICON SECURITY ENGINE INTERFACE (ISSEI) +M: Alexander Usyskin +S: Supported +F: Documentation/driver-api/issei/issei.rst +F: drivers/misc/issei/ +F: include/uapi/linux/issei.h + INTEL SGX M: Jarkko Sakkinen R: Dave Hansen diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 390256ed91f4..e594edf86941 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -651,4 +651,5 @@ source "drivers/misc/mchp_pci1xxxx/Kconfig" source "drivers/misc/keba/Kconfig" source "drivers/misc/amd-sbi/Kconfig" source "drivers/misc/rp1/Kconfig" +source "drivers/misc/issei/Kconfig" endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index fed47c7672b9..086ac3f75935 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -74,3 +74,4 @@ obj-$(CONFIG_MCHP_LAN966X_PCI) += lan966x-pci.o obj-y += keba/ obj-y += amd-sbi/ obj-$(CONFIG_MISC_RP1) += rp1/ +obj-$(CONFIG_INTEL_SSEI) += issei/ diff --git a/drivers/misc/issei/Kconfig b/drivers/misc/issei/Kconfig new file mode 100644 index 000000000000..d98ac7925ce6 --- /dev/null +++ b/drivers/misc/issei/Kconfig @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2023-2026 Intel Corporation +config INTEL_SSEI + tristate "Intel Silicon Security Engine Interface" + help + The ISSEI (Intel Silicon Security Engine Interface) + subsystem provides a communication channel between the host and the + Silicon Security Engine. + Enable this driver to get SPDM and other features on Intel client CPUs + released in 2024 (Lunar Lake) or later. + + If selected, the /dev/isseiX device will be created. + If in doubt, select N. diff --git a/drivers/misc/issei/Makefile b/drivers/misc/issei/Makefile new file mode 100644 index 000000000000..f13bcf3e1699 --- /dev/null +++ b/drivers/misc/issei/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2023-2026 Intel Corporation +ccflags-y += -DDEFAULT_SYMBOL_NAMESPACE='"INTEL_SSEI"' + +obj-$(CONFIG_INTEL_SSEI) += issei.o +issei-objs += cdev.o +issei-objs += dma.o diff --git a/drivers/misc/issei/cdev.c b/drivers/misc/issei/cdev.c new file mode 100644 index 000000000000..d3d53dad088e --- /dev/null +++ b/drivers/misc/issei/cdev.c @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2023-2026 Intel Corporation */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "issei_dev.h" +#include "cdev.h" + +struct class *issei_class; +static dev_t issei_devt; + +#define ISSEI_MAX_DEVS MINORMASK + +static DEFINE_XARRAY_ALLOC(issei_minor_xa); + +static ssize_t fw_ver_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct issei_device *idev = dev_get_drvdata(device); + + return sysfs_emit(buf, "%u.%u.%u.%u\n", idev->fw_version[0], idev->fw_version[1], + idev->fw_version[2], idev->fw_version[3]); +} +static DEVICE_ATTR_RO(fw_ver); + +static struct attribute *issei_attrs[] = { + &dev_attr_fw_ver.attr, + NULL +}; +ATTRIBUTE_GROUPS(issei); + +static const struct file_operations issei_fops = { + .owner = THIS_MODULE, +}; + +static void issei_device_release(struct device *dev) +{ + kfree(dev_get_drvdata(dev)); +} + +static void issei_device_init(struct issei_device *idev, struct device *parent, + const struct issei_dma_length *dma_length, + const struct issei_hw_ops *ops) +{ + idev->parent = parent; + idev->power_down = false; + init_waitqueue_head(&idev->wait_has_data); + idev->has_data = false; + init_waitqueue_head(&idev->wait_rst_state); + idev->rst_state = ISSEI_RST_STATE_INIT; + + mutex_init(&idev->client_lock); + INIT_LIST_HEAD(&idev->host_client_list); + idev->host_client_last_id = 0; + idev->host_client_count = 0; + INIT_LIST_HEAD(&idev->fw_client_list); + INIT_LIST_HEAD(&idev->write_queue); + idev->last_write_ts = 0; + + idev->dma.length = *dma_length; + + idev->ops = ops; +} + +/** + * issei_register: register issei character device + * @hw_size: size of the hardware structure to allocate + * @parent: parent device + * @dma_length: structure with DMA sizes + * @ops: hardware-related operations + * + * Return: pointer allocated to issei_device structure, error on failure + */ +struct issei_device *issei_register(size_t hw_size, struct device *parent, + const struct issei_dma_length *dma_length, + const struct issei_hw_ops *ops) +{ + struct issei_device *idev; + u32 minor; + int ret, devno; + + idev = kzalloc(sizeof(*idev) + hw_size, GFP_KERNEL); + if (!idev) + return ERR_PTR(-ENOMEM); + + issei_device_init(idev, parent, dma_length, ops); + + ret = xa_alloc(&issei_minor_xa, &minor, idev, XA_LIMIT(0, ISSEI_MAX_DEVS), GFP_KERNEL); + if (ret < 0) { + dev_err(&idev->dev, "Failed to allocate minor. ret = %d\n", ret); + kfree(idev); + return ERR_PTR(ret); + } + + idev->minor = minor; + devno = MKDEV(MAJOR(issei_devt), idev->minor); + + device_initialize(&idev->dev); + idev->dev.devt = devno; + idev->dev.class = issei_class; + idev->dev.parent = parent; + idev->dev.groups = issei_groups; + idev->dev.release = issei_device_release; + dev_set_drvdata(&idev->dev, idev); + + idev->cdev = cdev_alloc(); + if (!idev->cdev) { + ret = -ENOMEM; + goto err; + } + idev->cdev->ops = &issei_fops; + if (parent->driver) + idev->cdev->owner = parent->driver->owner; + cdev_set_parent(idev->cdev, &idev->dev.kobj); + + ret = cdev_add(idev->cdev, devno, 1); + if (ret) { + dev_err(parent, "unable to add device %d:%u ret = %d\n", + MAJOR(issei_devt), idev->minor, ret); + goto err_del_cdev; + } + + ret = dev_set_name(&idev->dev, "issei%u", idev->minor); + if (ret) { + dev_err(parent, "unable to set name to device %d:%u ret = %d\n", + MAJOR(issei_devt), idev->minor, ret); + goto err_del_cdev; + } + + ret = device_add(&idev->dev); + if (ret) { + dev_err(parent, "unable to add device %d:%u ret = %d\n", + MAJOR(issei_devt), idev->minor, ret); + goto err_del_cdev; + } + + idev->fw_clients = kset_create_and_add("fw_clients", NULL, &idev->dev.kobj); + if (!idev->fw_clients) { + ret = -ENOMEM; + goto err_del_dev; + } + + return idev; + +err_del_dev: + device_del(&idev->dev); +err_del_cdev: + cdev_del(idev->cdev); +err: + put_device(&idev->dev); + xa_erase(&issei_minor_xa, minor); + + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(issei_register); + +/** + * issei_deregister: remove issei character device + * @idev: the device structure + */ +void issei_deregister(struct issei_device *idev) +{ + u32 minor = idev->minor; + + cdev_del(idev->cdev); + + kset_unregister(idev->fw_clients); + + device_del(&idev->dev); + + put_device(&idev->dev); + + xa_erase(&issei_minor_xa, minor); +} +EXPORT_SYMBOL_GPL(issei_deregister); + +static int __init issei_cdev_init(void) +{ + int ret; + + issei_class = class_create("issei"); + if (IS_ERR(issei_class)) { + pr_err("couldn't create class\n"); + return PTR_ERR(issei_class); + } + + ret = alloc_chrdev_region(&issei_devt, 0, ISSEI_MAX_DEVS, "issei"); + if (ret < 0) { + pr_err("unable to allocate char dev region\n"); + class_destroy(issei_class); + return ret; + } + + return 0; +} + +static void __exit issei_cdev_exit(void) +{ + unregister_chrdev_region(issei_devt, ISSEI_MAX_DEVS); + class_destroy(issei_class); +} + +module_init(issei_cdev_init); +module_exit(issei_cdev_exit); + +MODULE_DESCRIPTION("Intel(R) Silicon Security Engine Interface"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/issei/cdev.h b/drivers/misc/issei/cdev.h new file mode 100644 index 000000000000..30075a624f2d --- /dev/null +++ b/drivers/misc/issei/cdev.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2023-2026 Intel Corporation */ +#ifndef _ISSEI_CDEV_H_ +#define _ISSEI_CDEV_H_ + +struct device; +struct issei_device; +struct issei_dma_length; +struct issei_hw_ops; + +struct issei_device *issei_register(size_t hw_size, struct device *parent, + const struct issei_dma_length *dma_length, + const struct issei_hw_ops *ops); +void issei_deregister(struct issei_device *idev); + +#endif /* _ISSEI_CDEV_H_ */ diff --git a/drivers/misc/issei/dma.c b/drivers/misc/issei/dma.c new file mode 100644 index 000000000000..457d28f31c06 --- /dev/null +++ b/drivers/misc/issei/dma.c @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2023-2026 Intel Corporation */ +#include +#include +#include +#include +#include +#include + +#include "issei_dev.h" +#include "hw_msg.h" + +static inline size_t __issei_dma_size(const struct issei_dma *dma) +{ + return dma->length.h2f + dma->length.f2h + dma->length.ctl; +} + +/** + * issei_dmam_setup - setup DMA buffer and clean it + * @idev: issei device object + * + * Return: 0 on success, <0 on failures + */ +int issei_dmam_setup(struct issei_device *idev) +{ + struct issei_dma *dma = &idev->dma; + size_t size; + + size = __issei_dma_size(dma); + if (!size) + return -EINVAL; + + if (!dma->vaddr) + dma->vaddr = dmam_alloc_coherent(idev->parent, size, &dma->daddr, + GFP_KERNEL | __GFP_ZERO); + if (dma->vaddr) + memset(dma->vaddr, 0, size); + return dma->vaddr ? 0 : -ENOMEM; +} + +static inline struct control_buffer *__dma_get_ctl_buf(struct issei_dma *dma) +{ + return dma->vaddr + dma->length.h2f + dma->length.f2h; +} + +static bool __issei_dma_is_read_busy(struct issei_dma *dma) +{ + struct control_buffer *ctl = __dma_get_ctl_buf(dma); + + return ctl->f2h_counter_wr != ctl->f2h_counter_rd; +} + +static bool __issei_dma_is_write_busy(struct issei_dma *dma) +{ + struct control_buffer *ctl = __dma_get_ctl_buf(dma); + + return ctl->h2f_counter_wr != ctl->h2f_counter_rd; +} + +static void __issei_dma_read_finalize(struct issei_device *idev) +{ + struct control_buffer *ctl = __dma_get_ctl_buf(&idev->dma); + + dev_dbg(&idev->dev, "ctl->f2h_counter_rd %u\n", ctl->f2h_counter_rd); + /* No need to check overflow - the firmware counters overflow the same way */ + ctl->f2h_counter_rd++; +} + +static void __issei_dma_write_finalize(struct issei_device *idev) +{ + struct control_buffer *ctl = __dma_get_ctl_buf(&idev->dma); + + dev_dbg(&idev->dev, "ctl->h2f_counter_wr %u\n", ctl->h2f_counter_wr); + /* No need to check overflow - the firmware counters overflow the same way */ + ctl->h2f_counter_wr++; +} + +/** + * issei_dma_write - write data package to DMA + * @idev: issei device object + * @data: data atructure + * + * Return: 0 on success, <0 on failures + */ +int issei_dma_write(struct issei_device *idev, const struct issei_dma_data *data) +{ + u8 *write_buf = idev->dma.vaddr; + struct ham_message_header *hdr = (struct ham_message_header *)write_buf; + + if (data->length > idev->dma.length.h2f - sizeof(*hdr)) { + dev_err(&idev->dev, "Message is too big\n"); + return -EMSGSIZE; + } + + if (__issei_dma_is_write_busy(&idev->dma)) { + if (ktime_ms_delta(ktime_get(), idev->last_write_ts) > ISSEI_WRITE_TIMEOUT_MSEC) { + dev_err(&idev->dev, "Write stuck in queue\n"); + return -EIO; + } + dev_info(&idev->dev, "Write is busy\n"); + return -EBUSY; + } + + hdr->length = data->length; + hdr->fw_id = data->fw_id; + hdr->host_id = data->host_id; + hdr->flags = data->flags; + hdr->status = data->status; + hdr->reserved = 0; + + memcpy(write_buf + sizeof(*hdr), data->buf, data->length); + + __issei_dma_write_finalize(idev); + idev->last_write_ts = ktime_get(); + return 0; +} + +/** + * issei_dma_read - read data package from DMA + * @idev: issei device object + * @data: data atructure + * + * Return: %0 on success, <0 on failures + */ +int issei_dma_read(struct issei_device *idev, struct issei_dma_data *data) +{ + u8 *read_buf = idev->dma.vaddr + idev->dma.length.h2f; + struct ham_message_header *hdr = (struct ham_message_header *)read_buf; + + if (!__issei_dma_is_read_busy(&idev->dma)) { + dev_dbg(&idev->dev, "Nothing to read\n"); + return -ENODATA; + } + + dev_dbg(&idev->dev, "Reading header\n"); + data->length = hdr->length; + data->fw_id = hdr->fw_id; + data->host_id = hdr->host_id; + data->flags = hdr->flags; + data->status = hdr->status; + + if (data->length > idev->dma.length.f2h - sizeof(*hdr)) { + dev_err(&idev->dev, "Message length %u is bigger than buffer %zu\n", + data->length, idev->dma.length.f2h - sizeof(*hdr)); + return -EIO; + } + + dev_dbg(&idev->dev, "Reading data (size %u)\n", data->length); + data->buf = kmemdup(read_buf + sizeof(*hdr), data->length, GFP_KERNEL); + if (!data->buf) + return -ENOMEM; + __issei_dma_read_finalize(idev); + return 0; +} diff --git a/drivers/misc/issei/dma.h b/drivers/misc/issei/dma.h new file mode 100644 index 000000000000..e6b7aeb50ae6 --- /dev/null +++ b/drivers/misc/issei/dma.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2023-2026 Intel Corporation */ +#ifndef _ISSEI_DMA_H_ +#define _ISSEI_DMA_H_ + +#include + +struct issei_device; + +/** + * struct issei_dma_length - sizes of DMA memory portions + * @h2f: host to firmware buffer size + * @f2h: firmware to host buffer size + * @ctl: control buffer size + */ +struct issei_dma_length { + size_t h2f; + size_t f2h; + size_t ctl; +}; + +/** + * struct issei_dma - DMA memory structure + * @vaddr: virtual address + * @daddr: physical address + * @length: memory sizes structure + */ +struct issei_dma { + void *vaddr; + dma_addr_t daddr; + struct issei_dma_length length; +}; + +/* Operation statuses */ +#define HAMS_SUCCESS 0x00 +#define HAMS_PROTOCOL_NOT_SUPPORTED 0x01 +#define HAMS_DEPRECATED_BUS_MSG 0x02 +#define HAMS_CLIENT_NOT_EXISTS 0x03 +#define HAMS_MSG_TOO_BIG 0x04 +#define HAMS_MSG_NOT_CONSUMED 0x05 +#define HAMS_CORRUPTED_BUS_MSG 0x06 +#define HAMS_CORRUPTED_HEADER 0x07 +#define HAMS_INVALID_LENGTH 0x08 +#define HAMS_SHARED_MEMORY_SIZE_UNSUPPORTED 0x09 +#define HAMS_GENERAL_FATAL_ERROR 0xff + +/** + * struct issei_dma_data - data passed through channel + * @fw_id: firmware client id + * @host_id: host client id + * @flags: flags bitmap + * @status: operation status + * @length: data length + * @buf: pointer to data buffer + */ +struct issei_dma_data { + u16 fw_id; + u16 host_id; + u32 flags; + u32 status; + u32 length; + void *buf; +}; + +int issei_dmam_setup(struct issei_device *idev); +int issei_dma_write(struct issei_device *idev, const struct issei_dma_data *data); +int issei_dma_read(struct issei_device *idev, struct issei_dma_data *data); + +#endif /*_ISSEI_DMA_H_*/ diff --git a/drivers/misc/issei/hw_msg.h b/drivers/misc/issei/hw_msg.h new file mode 100644 index 000000000000..28fd3775f64c --- /dev/null +++ b/drivers/misc/issei/hw_msg.h @@ -0,0 +1,163 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2023-2026 Intel Corporation */ +#ifndef _ISSEI_HW_MSG_H_ +#define _ISSEI_HW_MSG_H_ + +#include +#include + +#define HAM_CB_MESSAGE_ID_REQ 0x8086cafe +#define HAM_CB_MESSAGE_ID_RES 0xcafe8086 +#define HAM_CB_MESSAGE_VER 0x1 + +/** + * struct ham_setup_shared_memory_req - shared memory setup request + * @msg_id: message id, should be %HAM_CB_MESSAGE_ID_REQ + * @ver: message version (%HAM_CB_MESSAGE_VER) + * @reserved: reserved + * @buffer_physical_address: physical address of DMA buffer + * @host_to_fw_section_length: memory size for host to fw communication + * @fw_to_host_section_length: memory size for fw to host communication + * @control_length: memory size for control buffer + */ +struct ham_setup_shared_memory_req { + u32 msg_id; + u16 ver; + u16 reserved; + u64 buffer_physical_address; + u32 host_to_fw_section_length; + u32 fw_to_host_section_length; + u32 control_length; +} __packed __aligned(4); + +/** + * struct ham_setup_shared_memory_res - shared memory setup response + * @msg_id: message id, should be %HAM_CB_MESSAGE_ID_RES + * @status: operation status + */ +struct ham_setup_shared_memory_res { + u32 msg_id; + u32 status; +}; + +/** + * struct control_buffer - control buffer structure + * @h2f_counter_wr: write counter host to fw + * @h2f_counter_rd: read counter host to fw + * @f2h_counter_wr: write counter fw to host + * @f2h_counter_rd: read counter fw to host + */ +struct control_buffer { + u32 h2f_counter_wr; + u32 h2f_counter_rd; + u32 f2h_counter_wr; + u32 f2h_counter_rd; +}; + +/* HAM messages over DMA */ + +/** + * struct ham_message_header - message header over DMA + * @length: message length (payload only, not including header) + * @fw_id: firmware client id (0 means Bus Message) + * @host_id: host client id (0 means Bus Message) + * @flags: message flags + * @status: operation status + * @reserved: reserved + */ +struct ham_message_header { + u32 length; + u16 fw_id; + u16 host_id; + u32 flags; + u32 status; + u32 reserved; +}; + +/* Bus Commands */ +#define HAM_BUS_CMD_START_REQ 0x00 +#define HAM_BUS_CMD_START_RSP 0x80 +#define HAM_BUS_CMD_CLIENT_REQ 0x01 +#define HAM_BUS_CMD_CLIENT_RSP 0x81 + +/** + * struct ham_bus_message - bus message header + * @cmd: command code + */ +struct ham_bus_message { + u32 cmd; +}; + +#define HAM_SUPPORTED_VERSION 0x01 + +/** + * struct ham_start_message_req - start message + * @header: bus message header (%HAM_BUS_CMD_START_REQ) + * @supported_version: supported protocol version + * @heci_capabilities_length: protocol capabilities length in bytes + * @heci_capabilities: protocol capabilities data + */ +struct ham_start_message_req { + struct ham_bus_message header; + u16 supported_version; + u8 heci_capabilities_length; + u8 heci_capabilities[] __counted_by(heci_capabilities_length); +} __packed; + +/** + * struct ham_start_message_res - start message response + * @header: bus message header (%HAM_BUS_CMD_START_RSP) + * @fw_version: firmware version (four u16 blocks) + * @supported_version: supported protocol version + * @heci_capabilities_length: protocol capabilities length in bytes + * @heci_capabilities: protocol capabilities data + */ +struct ham_start_message_res { + struct ham_bus_message header; + u16 fw_version[4]; + u16 supported_version; + u8 heci_capabilities_length; + u8 heci_capabilities[] __counted_by(heci_capabilities_length); +} __packed; + +/** + * struct ham_get_clients_req - clients list request + * @header: bus message header (%HAM_BUS_CMD_CLIENT_REQ) + */ +struct ham_get_clients_req { + struct ham_bus_message header; +}; + +/** + * struct ham_client_properties - single client properties + * @client_number: client id in firmware + * @protocol_ver: client protocol version + * @reserved: reserved + * @client_uuid: protocol name (UUID) + * @client_mtu: max message length supported by client + * @flags: client flags + */ +struct ham_client_properties { + u16 client_number; + u8 protocol_ver; + u8 reserved; + uuid_t client_uuid; + u32 client_mtu; + u32 flags; +}; + +/** + * struct ham_get_clients_res - client properties response + * @header: bus message header (%HAM_BUS_CMD_CLIENT_RSP) + * @client_count: number of clients in firmware + * @reserved: reserved + * @clients_props: list of client properties + */ +struct ham_get_clients_res { + struct ham_bus_message header; + u16 client_count; + u16 reserved; + struct ham_client_properties clients_props[] __counted_by(client_count); +}; + +#endif /* _ISSEI_HW_MSG_H_ */ diff --git a/drivers/misc/issei/issei_dev.h b/drivers/misc/issei/issei_dev.h new file mode 100644 index 000000000000..c742e7fe6cb6 --- /dev/null +++ b/drivers/misc/issei/issei_dev.h @@ -0,0 +1,160 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2023-2026 Intel Corporation */ +#ifndef _ISSEI_DEV_H_ +#define _ISSEI_DEV_H_ + +#include +#include +#include +#include +#include +#include + +#include "dma.h" + +struct cdev; +struct kset; + +struct issei_device; +struct issei_host_client; + +extern struct class *issei_class; + +#define ISSEI_HOST_CLIENTS_MAX 255 + +#define ISSEI_SUPPORTED_PROTOCOL_VER 1 + +#define ISSEI_MAX_CONSEC_RESET 3 + +#define ISSEI_RST_HW_READY_TIMEOUT_MSEC (2 * MSEC_PER_SEC) +#define ISSEI_RST_STEP_TIMEOUT_MSEC (2 * MSEC_PER_SEC) +#define ISSEI_STOP_TIMEOUT_MSEC 500 +#define ISSEI_WRITE_TIMEOUT_MSEC (MSEC_PER_SEC) + +/** + * struct issei_write_buf - write buffer object + * @list: linked list pointer + * @cl: host client that requested this write + * @data: data to write + * @data_size: data size + */ +struct issei_write_buf { + struct list_head list; + struct issei_host_client *cl; + const u8 *data; + size_t data_size; +}; + +/** + * struct issei_hw_ops - callbacks for hardware operations + * @irq_clear: clear irq + * @irq_enable: enable irq + * @irq_disable: disable irq + * @irq_sync: sync irq + * @hw_reset: initiate hardware reset + * @hw_config: initial hardware config + * @hw_is_ready: check if hardware is ready + * @hw_reset_release: release hardware from reset + * @host_set_ready: set host ready indicator + * @setup_message_send: send setup message + * @setup_message_recv: receive setup message + * @irq_write_generate: generate interrupt on write complete + */ +struct issei_hw_ops { + void (*irq_clear)(struct issei_device *idev); + void (*irq_enable)(struct issei_device *idev); + void (*irq_disable)(struct issei_device *idev); + void (*irq_sync)(struct issei_device *idev); + int (*hw_reset)(struct issei_device *idev, bool enable); + int (*hw_config)(struct issei_device *idev); + bool (*hw_is_ready)(struct issei_device *idev); + void (*hw_reset_release)(struct issei_device *idev); + void (*host_set_ready)(struct issei_device *idev); + int (*setup_message_send)(struct issei_device *idev); + int (*setup_message_recv)(struct issei_device *idev); + int (*irq_write_generate)(struct issei_device *idev); +}; + +/** + * enum issei_rst_state: driver reset flow states + * @ISSEI_RST_STATE_INIT: initial state + * @ISSEI_RST_STATE_HW_READY: waiting for HW to be ready + * @ISSEI_RST_STATE_SETUP: waiting for channel setup completion + * @ISSEI_RST_STATE_START: waiting for start handshake completion + * @ISSEI_RST_STATE_CLIENT_ENUM: waiting for client enumeration + * @ISSEI_RST_STATE_DONE: reset flow is done + * @ISSEI_RST_STATE_DISABLED: flow is disabled + */ +enum issei_rst_state { + ISSEI_RST_STATE_INIT, + ISSEI_RST_STATE_HW_READY, + ISSEI_RST_STATE_SETUP, + ISSEI_RST_STATE_START, + ISSEI_RST_STATE_CLIENT_ENUM, + ISSEI_RST_STATE_DONE, + ISSEI_RST_STATE_DISABLED, +}; + +/** + * struct issei_device - issei device + * @parent: parent device object + * @dev: associated device object + * @cdev: character device + * @minor: allocated minor number + * @wait_has_data: wait queue for data + * @has_data: there are data to process + * @power_down: device is powering down + * @wait_rst_state: waitqueue for reset state processing + * @rst_state: reset state + * @fw_protocol_ver: protocol version + * @fw_version: firmware version + * @process_thread: worker thread + * @reset_count: number of consecutive link reset attempts + * @all_reset_count: cumilative number of link reset attempts + * @client_lock: mutex to protect client lists and write queue + * @host_client_list: host clients list + * @host_client_last_id: last allocated host client id + * @host_client_count: number of active host clients + * @fw_client_list: firmware clients list + * @write_queue: write queue + * @last_write_ts: last write timestamp + * @dma: DMA memory configuration + * @ops: hardware operations + * @hw: hw-specific data + */ +struct issei_device { + struct device *parent; + struct device dev; + struct cdev *cdev; + u32 minor; + wait_queue_head_t wait_has_data; + bool has_data; + bool power_down; + wait_queue_head_t wait_rst_state; + enum issei_rst_state rst_state; + u16 fw_protocol_ver; + u16 fw_version[4]; + /* reset flow */ + struct task_struct *process_thread; + u8 reset_count; + u8 all_reset_count; + /* clients */ + struct mutex client_lock; + struct list_head host_client_list; + u16 host_client_last_id; + u8 host_client_count; + struct kset *fw_clients; + struct list_head fw_client_list; + struct list_head write_queue; + ktime_t last_write_ts; + struct issei_dma dma; + const struct issei_hw_ops *ops; + char hw[]; +}; + +static inline void issei_poke_process_thread(struct issei_device *idev) +{ + WRITE_ONCE(idev->has_data, true); + wake_up_interruptible(&idev->wait_has_data); +} +#endif /* _ISSEI_DEV_H_ */ diff --git a/include/uapi/linux/issei.h b/include/uapi/linux/issei.h new file mode 100644 index 000000000000..3bfb89330265 --- /dev/null +++ b/include/uapi/linux/issei.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Copyright (C) 2023-2026 Intel Corporation + * Intel Silicon Security Engine Interface (ISSEI) Linux driver: + * ISSEI Interface Header + */ +#ifndef _LINUX_ISSEI_H +#define _LINUX_ISSEI_H + +#include +#include + +/* + * This ioctl is used to associate the current file descriptor with a + * FW Client (given by UUID). This opens a communication channel + * between a host client and a FW client. From this point every read and write + * will communicate with the associated FW client. + * The communication between the clients can be terminated by + * IOCTL_ISSEI_DISCONNECT_CLIENT IOCTL or by + * closing the file descriptor (file_operation release()). + * + * The ioctl argument is a struct with a union that contains + * the input parameter and the output parameter for this ioctl. + * + * The input parameter is UUID of the FW Client. + * The output parameter is the properties of the FW client + * (FW protocol version, max message size and client flags). + */ +#define IOCTL_ISSEI_CONNECT_CLIENT \ + _IOWR('H', 0x01, struct issei_connect_client_data) + +/** + * struct issei_client - ISSEI client information structure + * @max_msg_length: maximum message length supported by the firmware client (in bytes) + * @protocol_version: protocol version reported by the firmware client + * @reserved1: reserved + * @flags: flag bitmask reported by the firmware client + * @reserved2: reserved + */ +struct issei_client { + __u32 max_msg_length; + __u8 protocol_version; + __u8 reserved1[3]; + __u32 flags; + __u32 reserved2; +}; + +#define ISSEI_IOCTL_UUID_LEN 16 + +/** + * struct issei_connect_client_data - ioctl Connect Client Data structure + * @in_client_uuid: unique id of the firmware client to connect to (from user space to kernel) + * @out_client_properties: connected firmware client properties (from kernel to user space) + */ +struct issei_connect_client_data { + union { + __u8 in_client_uuid[ISSEI_IOCTL_UUID_LEN]; + struct issei_client out_client_properties; + }; +}; + +/* + * This ioctl is used to terminate association between + * the host client and the FW client. + */ +#define IOCTL_ISSEI_DISCONNECT_CLIENT \ + _IO('H', 0x02) + +#endif /* _LINUX_ISSEI_H */ From 2207e8d48088e7358dd6b08163dac95730cc1c66 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Wed, 13 May 2026 17:18:43 +0300 Subject: [PATCH 086/135] issei: add firmware and host clients implementation, finish character device Add the core implementation for firmware and host client management within the ISSEI (Intel Silicon Security Engine Interface) subsystem support for a character device to expose the ISSEI HECI interface to user space. The firmware client (fw_client) and host client (host_client) modules are responsible for managing communication between the host software and the firmware. The character device provides a communication channel for user-space applications to interact with the firmware on the platform. The client modules enable the ISSEI driver to manage multiple host clients communicating with corresponding firmware clients, facilitating data transfers and control operations over the HECI interface. The character device allows user-space applications to establish connections to firmware clients using UUIDs, exchange messages, and control the communication flow using standard file operation calls. Reviewed-by: Karol Wachowski Co-developed-by: Vitaly Lubart Signed-off-by: Vitaly Lubart Signed-off-by: Alexander Usyskin Link: https://patch.msgid.link/20260513-issei-for-upstream-v1-2-f590038678f9@intel.com Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-class-issei | 73 +++ MAINTAINERS | 1 + drivers/misc/issei/Makefile | 2 + drivers/misc/issei/cdev.c | 227 +++++++++ drivers/misc/issei/fw_client.c | 240 +++++++++ drivers/misc/issei/fw_client.h | 45 ++ drivers/misc/issei/host_client.c | 519 ++++++++++++++++++++ drivers/misc/issei/host_client.h | 75 +++ 8 files changed, 1182 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-class-issei create mode 100644 drivers/misc/issei/fw_client.c create mode 100644 drivers/misc/issei/fw_client.h create mode 100644 drivers/misc/issei/host_client.c create mode 100644 drivers/misc/issei/host_client.h diff --git a/Documentation/ABI/testing/sysfs-class-issei b/Documentation/ABI/testing/sysfs-class-issei new file mode 100644 index 000000000000..73a01f4627cb --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-issei @@ -0,0 +1,73 @@ +What: /sys/class/issei/ +Date: June 2026 +KernelVersion: 7.2 +Contact: Alexander Usyskin +Description: + The issei/ class sub-directory belongs to issei device class + +What: /sys/class/issei/issei/ +Date: June 2026 +KernelVersion: 7.2 +Contact: Alexander Usyskin +Description: + The /sys/class/issei/isseiN directory is created for + each probed issei device + +What: /sys/class/issei/issei/fw_ver +Date: June 2026 +KernelVersion: 7.2 +Contact: Alexander Usyskin +Description: Display the ISSE firmware version. + + The version of the ISSE firmware is in format: + .... + +What: /sys/class/issei/issei/fw_clients +Date: June 2026 +KernelVersion: 7.2 +Contact: Alexander Usyskin +Description: + The fw_clients directory stores all firmware clients on the + probed issei device + +What: /sys/class/issei/issei/fw_clients/ +Date: June 2026 +KernelVersion: 7.2 +Contact: Alexander Usyskin +Description: + The /sys/class/issei/isseiN/fw_client/M directory is created for + each firmware client on the probed issei device where M is the + id of firmware client. + +What: /sys/class/issei/issei/fw_clients//id +Date: June 2026 +KernelVersion: 7.2 +Contact: Alexander Usyskin +Description: Displays id of the firmware client + + The id of firmware client is it's number in client enumeration order, + starting from 1. + +What: /sys/class/issei/issei/fw_clients//uuid +Date: June 2026 +KernelVersion: 7.2 +Contact: Alexander Usyskin +Description: Displays uuid of the firmware client + + The universally unique identifier of the firmware client + +What: /sys/class/issei/issei/fw_clients//mtu +Date: June 2026 +KernelVersion: 7.2 +Contact: Alexander Usyskin +Description: Displays maximum transmission unit of the firmware client + + The maximum transmission unit (in bytes) used by the firmware client. + +What: /sys/class/issei/issei/fw_clients//ver +Date: June 2026 +KernelVersion: 7.2 +Contact: Alexander Usyskin +Description: Displays version of the firmware client + + The version of the firmware client diff --git a/MAINTAINERS b/MAINTAINERS index b6bf86f9f8ab..a3edb1fa7954 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13363,6 +13363,7 @@ F: tools/testing/selftests/drivers/sdsi/ INTEL SILICON SECURITY ENGINE INTERFACE (ISSEI) M: Alexander Usyskin S: Supported +F: Documentation/ABI/testing/sysfs-class-issei F: Documentation/driver-api/issei/issei.rst F: drivers/misc/issei/ F: include/uapi/linux/issei.h diff --git a/drivers/misc/issei/Makefile b/drivers/misc/issei/Makefile index f13bcf3e1699..1471ed99d619 100644 --- a/drivers/misc/issei/Makefile +++ b/drivers/misc/issei/Makefile @@ -5,3 +5,5 @@ ccflags-y += -DDEFAULT_SYMBOL_NAMESPACE='"INTEL_SSEI"' obj-$(CONFIG_INTEL_SSEI) += issei.o issei-objs += cdev.o issei-objs += dma.o +issei-objs += fw_client.o +issei-objs += host_client.o diff --git a/drivers/misc/issei/cdev.c b/drivers/misc/issei/cdev.c index d3d53dad088e..b7d65e2d0813 100644 --- a/drivers/misc/issei/cdev.c +++ b/drivers/misc/issei/cdev.c @@ -2,6 +2,7 @@ /* Copyright (C) 2023-2026 Intel Corporation */ #include #include +#include #include #include #include @@ -11,11 +12,15 @@ #include #include #include +#include +#include +#include #include #include #include #include "issei_dev.h" +#include "host_client.h" #include "cdev.h" struct class *issei_class; @@ -25,6 +30,221 @@ static dev_t issei_devt; static DEFINE_XARRAY_ALLOC(issei_minor_xa); +static int issei_open(struct inode *inode, struct file *fp) +{ + struct issei_host_client *cl; + struct issei_device *idev; + + xa_lock(&issei_minor_xa); + idev = xa_load(&issei_minor_xa, iminor(inode)); + if (idev) + get_device(&idev->dev); + xa_unlock(&issei_minor_xa); + if (!idev) + return -ENODEV; + + cl = issei_cl_create(idev, fp); + if (IS_ERR(cl)) { + put_device(&idev->dev); + return PTR_ERR(cl); + } + fp->private_data = cl; + + return nonseekable_open(inode, fp); +} + +static int issei_release(struct inode *inode, struct file *fp) +{ + struct issei_host_client *cl = fp->private_data; + struct issei_device *idev = cl->idev; + + issei_cl_remove(cl); + put_device(&idev->dev); + + return 0; +} + +static long issei_ioctl(struct file *file, unsigned int cmd, unsigned long data) +{ + struct issei_host_client *cl = file->private_data; + struct issei_connect_client_data conn; + struct issei_device *idev = cl->idev; + int ret; + + switch (cmd) { + case IOCTL_ISSEI_CONNECT_CLIENT: + dev_dbg(&idev->dev, "IOCTL_ISSEI_CONNECT_CLIENT\n"); + + if (idev->rst_state != ISSEI_RST_STATE_DONE) { + dev_dbg(&idev->dev, "Device is in transition\n"); + return -ENODEV; + } + + if (copy_from_user(&conn, (char __user *)data, sizeof(conn))) { + dev_dbg(&idev->dev, "failed to copy data from userland\n"); + return -EFAULT; + } + + ret = issei_cl_connect(cl, (uuid_t *)&conn.in_client_uuid, + &conn.out_client_properties.max_msg_length, + &conn.out_client_properties.protocol_version, + &conn.out_client_properties.flags); + if (ret) + return ret; + + if (copy_to_user((char __user *)data, &conn, sizeof(conn))) { + dev_dbg(&idev->dev, "failed to copy data to userland\n"); + issei_cl_disconnect(cl); + return -EFAULT; + } + return 0; + + case IOCTL_ISSEI_DISCONNECT_CLIENT: + dev_dbg(&idev->dev, "IOCTL_ISSEI_DISCONNECT_CLIENT\n"); + + if (idev->rst_state != ISSEI_RST_STATE_DONE) { + dev_dbg(&idev->dev, "Device is in transition\n"); + return -ENODEV; + } + + return issei_cl_disconnect(cl); + + default: + return -ENOIOCTLCMD; + } +} + +static ssize_t issei_write(struct file *file, const char __user *ubuf, + size_t length, loff_t *offset) +{ + struct issei_host_client *cl = file->private_data; + struct issei_device *idev = cl->idev; + ssize_t ret; + + if (!length) + return 0; + + if (idev->rst_state != ISSEI_RST_STATE_DONE) { + dev_dbg(&idev->dev, "Device is in transition\n"); + return -EBUSY; + } + + /* sanity check */ + if (length > idev->dma.length.h2f) { + dev_dbg(&idev->dev, "Write is too big %zu > %zu\n", + length, idev->dma.length.h2f); + return -EFBIG; + } + + u8 *buf __free(kfree) = memdup_user(ubuf, length); + if (IS_ERR(buf)) { + dev_dbg(&idev->dev, "failed to copy data from userland\n"); + return PTR_ERR(buf); + } + + do { + ret = issei_cl_write(cl, buf, length); + if (ret < 0 && ret != -EAGAIN) + return ret; + /* buf is consumed by issei_cl_write on success */ + if (ret >= 0) + retain_and_null_ptr(buf); + if (wait_event_interruptible(cl->write_wait, issei_cl_check_write(cl) != 1)) { + issei_cl_clean_all_wbuf(cl); + if (signal_pending(current)) + return -EINTR; + return -ERESTARTSYS; + } + } while (ret == -EAGAIN); + + return ret; +} + +static ssize_t issei_read(struct file *file, char __user *ubuf, + size_t length, loff_t *offset) +{ + struct issei_host_client *cl = file->private_data; + struct issei_device *idev = cl->idev; + u8 *data = NULL; + ssize_t ret; + + if (!length) + return 0; + + if (idev->rst_state != ISSEI_RST_STATE_DONE) { + dev_dbg(&idev->dev, "Device is in transition\n"); + return -EBUSY; + } + + /* sanity check */ + if (length > idev->dma.length.f2h) { + dev_dbg(&idev->dev, "Read is too big %zu > %zu\n", + length, idev->dma.length.f2h); + return -EFBIG; + } + + ret = issei_cl_read(cl, &data, length); + if (ret < 0) { + if (ret != -ENOENT) + return ret; + + if (wait_event_interruptible(cl->read_wait, issei_cl_check_read(cl) != 0)) { + if (signal_pending(current)) + return -EINTR; + return -ERESTARTSYS; + } + + ret = issei_cl_read(cl, &data, length); + if (ret < 0) + return ret; + } + + if (copy_to_user(ubuf, data, ret)) { + dev_dbg(&idev->dev, "failed to copy data to userland\n"); + ret = -EFAULT; + } else { + *offset = 0; + } + + kfree(data); + + return ret; +} + +static __poll_t issei_poll(struct file *file, poll_table *wait) +{ + __poll_t req_events = poll_requested_events(wait); + struct issei_host_client *cl = file->private_data; + struct issei_device *idev = cl->idev; + __poll_t mask = 0; + int ret; + + if (idev->rst_state != ISSEI_RST_STATE_DONE) { + dev_dbg(&idev->dev, "Device is in transition\n"); + return EPOLLERR; + } + + if (req_events & (EPOLLIN | EPOLLRDNORM)) { + poll_wait(file, &cl->read_wait, wait); + ret = issei_cl_check_read(cl); + if (ret == 1) + mask |= EPOLLIN | EPOLLRDNORM; + else if (ret < 0) + mask |= EPOLLERR; + } + + if (req_events & (EPOLLOUT | EPOLLWRNORM)) { + poll_wait(file, &cl->write_wait, wait); + ret = issei_cl_check_write(cl); + if (ret == 0) + mask |= EPOLLOUT | EPOLLWRNORM; + else if (ret < 0) + mask |= EPOLLERR; + } + + return mask; +} + static ssize_t fw_ver_show(struct device *device, struct device_attribute *attr, char *buf) { @@ -43,6 +263,13 @@ ATTRIBUTE_GROUPS(issei); static const struct file_operations issei_fops = { .owner = THIS_MODULE, + .open = issei_open, + .unlocked_ioctl = issei_ioctl, + .compat_ioctl = compat_ptr_ioctl, + .write = issei_write, + .read = issei_read, + .release = issei_release, + .poll = issei_poll, }; static void issei_device_release(struct device *dev) diff --git a/drivers/misc/issei/fw_client.c b/drivers/misc/issei/fw_client.c new file mode 100644 index 000000000000..b8e48dbf1c9c --- /dev/null +++ b/drivers/misc/issei/fw_client.c @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2023-2026 Intel Corporation */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "issei_dev.h" +#include "fw_client.h" + +/* + * Specific attribute handlers for fw_clients kset. + * Provide only show function as all fw_client attributes are read-only. + */ + +struct issei_fw_cl_attr { + struct attribute attr; + ssize_t (*show)(struct issei_fw_client *fw_cl, const struct issei_fw_cl_attr *attr, + char *buf); +}; +#define to_issei_fw_cl_attr(x) container_of_const(x, struct issei_fw_cl_attr, attr) + +static ssize_t fw_cl_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) +{ + const struct issei_fw_cl_attr *issei_attr; + struct issei_fw_client *fw_cl; + + issei_attr = to_issei_fw_cl_attr(attr); + fw_cl = to_issei_fw_client(kobj); + + if (!issei_attr->show) + return -EIO; + + return issei_attr->show(fw_cl, issei_attr, buf); +} + +static const struct sysfs_ops fw_cl_sysfs_ops = { + .show = fw_cl_attr_show, +}; + +#define FW_CL_ATTR_RO(_name) \ + struct issei_fw_cl_attr fw_cl_attr_##_name = __ATTR_RO(_name) + +/* fw_client attributes */ + +static ssize_t id_show(struct issei_fw_client *fw_cl, + const struct issei_fw_cl_attr *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", fw_cl->id); +} +static FW_CL_ATTR_RO(id); + +static ssize_t ver_show(struct issei_fw_client *fw_cl, + const struct issei_fw_cl_attr *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", fw_cl->ver); +} +static FW_CL_ATTR_RO(ver); + +static ssize_t uuid_show(struct issei_fw_client *fw_cl, + const struct issei_fw_cl_attr *attr, char *buf) +{ + return sysfs_emit(buf, "%pUb\n", &fw_cl->uuid); +} +static FW_CL_ATTR_RO(uuid); + +static ssize_t mtu_show(struct issei_fw_client *fw_cl, + const struct issei_fw_cl_attr *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", fw_cl->mtu); +} +static FW_CL_ATTR_RO(mtu); + +static const struct attribute *const fw_cl_attrs[] = { + &fw_cl_attr_id.attr, + &fw_cl_attr_ver.attr, + &fw_cl_attr_uuid.attr, + &fw_cl_attr_mtu.attr, + NULL, +}; + +static const struct attribute_group fw_cl_group = { + .attrs_const = fw_cl_attrs, +}; +__ATTRIBUTE_GROUPS(fw_cl); + +static void issei_fw_cl_init(struct issei_fw_client *fw_cl, u16 id, u8 ver, const uuid_t *uuid, + u32 mtu, u32 flags) +{ + INIT_LIST_HEAD(&fw_cl->list); + fw_cl->id = id; + fw_cl->ver = ver; + fw_cl->uuid = *uuid; + fw_cl->mtu = mtu; + fw_cl->flags = flags; +} + +static void fw_cl_release(struct kobject *kobj) +{ + struct issei_fw_client *fw_cl = to_issei_fw_client(kobj); + + kfree(fw_cl); +} + +static const struct kobj_type fw_client_ktype = { + .sysfs_ops = &fw_cl_sysfs_ops, + .release = fw_cl_release, + .default_groups = fw_cl_groups, +}; + +/** + * issei_fw_cl_create - create firmware client object and add to list + * @idev: issei device object + * @id: firmware client id + * @ver: firmware client version + * @uuid: firmware client unique id + * @mtu: firmware client maximum message size + * @flags: firmware client flags + * + * Should be called under idev->client_lock + * + * Return: pointer to newly created object on success, ERR_PTR on failure + */ +struct issei_fw_client *issei_fw_cl_create(struct issei_device *idev, u16 id, u8 ver, + const uuid_t *uuid, u32 mtu, u32 flags) +{ + int ret; + struct issei_fw_client *fw_cl = kzalloc_obj(*fw_cl); + + if (!fw_cl) + return ERR_PTR(-ENOMEM); + + WARN_ON(!mutex_is_locked(&idev->client_lock)); + + issei_fw_cl_init(fw_cl, id, ver, uuid, mtu, flags); + fw_cl->kobj.kset = idev->fw_clients; + + ret = kobject_init_and_add(&fw_cl->kobj, &fw_client_ktype, NULL, "%u", id); + if (ret) { + kobject_put(&fw_cl->kobj); + return ERR_PTR(ret); + } + + list_add_tail(&fw_cl->list, &idev->fw_client_list); + + dev_dbg(&idev->dev, "FW client %pUb created\n", uuid); + + kobject_uevent(&fw_cl->kobj, KOBJ_ADD); + + return fw_cl; +} + +static void __issei_fw_cl_remove(struct issei_device *idev, struct issei_fw_client *fw_cl) +{ + WARN(fw_cl->cl, "Removing connected client!\n"); + + dev_dbg(&idev->dev, "FW client %pUb will be removed\n", &fw_cl->uuid); + + list_del(&fw_cl->list); + kobject_put(&fw_cl->kobj); +} + +/** + * issei_fw_cl_remove_all - remove all firmware client objects + * @idev: issei device object + */ +void issei_fw_cl_remove_all(struct issei_device *idev) +{ + struct issei_fw_client *fw_cl, *next; + + guard(mutex)(&idev->client_lock); + + list_for_each_entry_safe(fw_cl, next, &idev->fw_client_list, list) + __issei_fw_cl_remove(idev, fw_cl); +} + +/** + * issei_fw_cl_find_by_uuid - find firmware client by uuid + * @idev: issei device object + * @uuid: uuid to search by it + * + * Should be called under idev->client_lock + * + * Return: pointer to firmware client object if found, NULL on failure + */ +struct issei_fw_client *issei_fw_cl_find_by_uuid(struct issei_device *idev, const uuid_t *uuid) +{ + struct issei_fw_client *fw_cl; + + WARN_ON(!mutex_is_locked(&idev->client_lock)); + + list_for_each_entry(fw_cl, &idev->fw_client_list, list) { + if (uuid_equal(&fw_cl->uuid, uuid)) { + kobject_get(&fw_cl->kobj); + return fw_cl; + } + } + return NULL; +} + +/** + * issei_fw_cl_connect - connect firmware and host client + * @fw_cl: firmware client + * @cl: host client + * + * Should be called under idev->client_lock + * + * Return: 0 on success, -EBUSY if already connected + */ +int issei_fw_cl_connect(struct issei_fw_client *fw_cl, struct issei_host_client *cl) +{ + if (fw_cl->cl) + return -EBUSY; + + kobject_get(&fw_cl->kobj); + fw_cl->cl = cl; + return 0; +} + +/** + * issei_fw_cl_disconnect - disconnect firmware and host client + * @fw_cl: firmware client + * + * Should be called under idev->client_lock + */ +void issei_fw_cl_disconnect(struct issei_fw_client *fw_cl) +{ + WARN_ON(!fw_cl->cl); + + fw_cl->cl = NULL; + kobject_put(&fw_cl->kobj); +} diff --git a/drivers/misc/issei/fw_client.h b/drivers/misc/issei/fw_client.h new file mode 100644 index 000000000000..377f733d7e91 --- /dev/null +++ b/drivers/misc/issei/fw_client.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2023-2026 Intel Corporation */ +#ifndef _ISSEI_FW_CLIENT_H_ +#define _ISSEI_FW_CLIENT_H_ + +#include +#include +#include +#include + +struct issei_device; +struct issei_host_client; + +/** + * struct issei_fw_client - represents firmware queue + * @kobj: associated kobject + * @list: link in firmware clients list + * @id: firmware client id + * @ver: firmware client version + * @uuid: firmware client protocol id + * @mtu: firmware client maximum buffer size + * @flags: firmware client flags + * @cl: pointer to host client, if connected + */ +struct issei_fw_client { + struct kobject kobj; + struct list_head list; + u16 id; + u8 ver; + uuid_t uuid; + u32 mtu; + u32 flags; + struct issei_host_client *cl; +}; +#define to_issei_fw_client(x) container_of(x, struct issei_fw_client, kobj) + +struct issei_fw_client *issei_fw_cl_create(struct issei_device *idev, u16 id, u8 ver, + const uuid_t *uuid, u32 mtu, u32 flags); +void issei_fw_cl_remove_all(struct issei_device *idev); +struct issei_fw_client *issei_fw_cl_find_by_uuid(struct issei_device *idev, const uuid_t *uuid); + +int issei_fw_cl_connect(struct issei_fw_client *fw_cl, struct issei_host_client *cl); +void issei_fw_cl_disconnect(struct issei_fw_client *fw_cl); + +#endif /* _ISSEI_FW_CLIENT_H_ */ diff --git a/drivers/misc/issei/host_client.c b/drivers/misc/issei/host_client.c new file mode 100644 index 000000000000..8f36d1c319ec --- /dev/null +++ b/drivers/misc/issei/host_client.c @@ -0,0 +1,519 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2023-2026 Intel Corporation */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fw_client.h" +#include "host_client.h" +#include "issei_dev.h" + +static inline u8 __issei_cl_fw_id(const struct issei_host_client *cl) +{ + return cl->fw_cl ? cl->fw_cl->id : 0; +} + +#define ISSEI_CL_FMT "cl:host=%02d fw=%02d " + +#define cl_dbg(_dev_, _cl_, format, arg...) do { \ + struct issei_host_client *_l_cl_ = _cl_; \ + dev_dbg(&(_dev_)->dev, ISSEI_CL_FMT format, _l_cl_->id, \ + __issei_cl_fw_id(_l_cl_), ##arg); \ +} while (0) + +#define cl_warn(_dev_, _cl_, format, arg...) do { \ + struct issei_host_client *_l_cl_ = _cl_; \ + dev_warn(&(_dev_)->dev, ISSEI_CL_FMT format, _l_cl_->id, \ + __issei_cl_fw_id(_l_cl_), ##arg); \ +} while (0) + +#define cl_err(_dev_, _cl_, format, arg...) do { \ + struct issei_host_client *_l_cl_ = _cl_; \ + dev_err(&(_dev_)->dev, ISSEI_CL_FMT format, _l_cl_->id, \ + __issei_cl_fw_id(_l_cl_), ##arg); \ +} while (0) + +static void __issei_cl_clean_wbuf(struct issei_write_buf *wbuf) +{ + list_del(&wbuf->list); + kfree(wbuf->data); + kfree(wbuf); +} + +static void __issei_cl_release_rbuf(struct issei_host_client *cl) +{ + cl->read_data = NULL; + cl->read_data_size = 0; +} + +static void __issei_cl_clean_rbuf(struct issei_host_client *cl) +{ + kfree(cl->read_data); + __issei_cl_release_rbuf(cl); +} + +static void __issei_cl_clean_all_wbuf(struct issei_device *idev, struct issei_host_client *cl) +{ + struct issei_write_buf *wbuf, *next; + + if (!cl->write_in_progress) + return; + list_for_each_entry_safe(wbuf, next, &idev->write_queue, list) { + if (wbuf->cl == cl) { + __issei_cl_clean_wbuf(wbuf); + break; + } + } + cl->write_in_progress = false; + /* synchronized under host client mutex */ + if (waitqueue_active(&cl->write_wait)) + wake_up_interruptible(&cl->write_wait); +} + +static struct issei_host_client *__issei_cl_by_id(struct issei_device *idev, u16 id) +{ + struct issei_host_client *cl; + + list_for_each_entry(cl, &idev->host_client_list, list) { + if (cl->id == id) + return cl; + } + return NULL; +} + +static void __issei_cl_disconnect(struct issei_device *idev, struct issei_host_client *cl) +{ + if (cl->state == ISSEI_HOST_CL_STATE_DISCONNECTED) + return; + + __issei_cl_clean_all_wbuf(idev, cl); + + if (!WARN_ON(!cl->fw_cl)) { + issei_fw_cl_disconnect(cl->fw_cl); + cl->fw_cl = NULL; + } + cl->state = ISSEI_HOST_CL_STATE_DISCONNECTED; + + if (cl->read_data) + __issei_cl_clean_rbuf(cl); + /* synchronized under host client mutex */ + if (waitqueue_active(&cl->read_wait)) + wake_up_interruptible(&cl->read_wait); + cl_dbg(idev, cl, "Disconnected\n"); +} + +static void __issei_cl_init(struct issei_host_client *cl, struct issei_device *idev, + u16 id, struct file *fp) +{ + INIT_LIST_HEAD(&cl->list); + cl->idev = idev; + cl->id = id; + cl->state = ISSEI_HOST_CL_STATE_DISCONNECTED; + cl->fp = fp; + init_waitqueue_head(&cl->write_wait); + init_waitqueue_head(&cl->read_wait); + __issei_cl_release_rbuf(cl); +} + +/** + * issei_cl_create - create the host client + * @idev: issei device + * @fp: file pointer to associate with host client + * + * Return: client pointer on success, ERR_PTR on error + */ +struct issei_host_client *issei_cl_create(struct issei_device *idev, struct file *fp) +{ + struct issei_host_client *cl; + u16 id; + + guard(mutex)(&idev->client_lock); + + if (idev->host_client_count == ISSEI_HOST_CLIENTS_MAX) { + dev_err(&idev->dev, "Maximum open clients %d is reached.\n", + ISSEI_HOST_CLIENTS_MAX); + return ERR_PTR(-EMFILE); + } + + do { + if (check_add_overflow(idev->host_client_last_id, 1, &id)) /* overflow */ + id = 1; + idev->host_client_last_id = id; + /* Not an endless loop as we have less clients then id's */ + } while (__issei_cl_by_id(idev, id)); + + cl = kzalloc_obj(*cl); + if (!cl) + return ERR_PTR(-ENOMEM); + + __issei_cl_init(cl, idev, id, fp); + list_add_tail(&cl->list, &idev->host_client_list); + idev->host_client_count++; + + cl_dbg(idev, cl, "Created\n"); + return cl; +} + +/** + * issei_cl_remove - disconnect and free the host client + * @cl: host client + */ +void issei_cl_remove(struct issei_host_client *cl) +{ + struct issei_device *idev; + + /* don't shout on error exit path */ + if (!cl) + return; + + idev = cl->idev; + + guard(mutex)(&idev->client_lock); + + idev->host_client_count--; + list_del(&cl->list); + + __issei_cl_disconnect(idev, cl); + + cl_dbg(idev, cl, "Removed\n"); + kfree(cl); +} + +/** + * issei_cl_connect - connect between FW and host client + * @cl: host client + * @uuid: FW client unique ID + * @mtu: memory for FW client max message size + * @ver: memory for FW client version + * @flags: memory for FW client flags + * + * Search for firmware client by UUID and connect it to provided + * host client, if not already connected to some client. + * + * Return: 0 on success, <0 on error + */ +int issei_cl_connect(struct issei_host_client *cl, const uuid_t *uuid, u32 *mtu, u8 *ver, + u32 *flags) +{ + struct issei_device *idev = cl->idev; + struct issei_fw_client *fw_cl; + int ret; + + guard(mutex)(&idev->client_lock); + + if (cl->state == ISSEI_HOST_CL_STATE_CONNECTED) { + cl_err(idev, cl, "Already connected\n"); + return -EISCONN; + } + + fw_cl = issei_fw_cl_find_by_uuid(idev, uuid); + if (!fw_cl) { + cl_dbg(idev, cl, "FW client %pUb not found\n", uuid); + return -ENOTTY; + } + + ret = issei_fw_cl_connect(fw_cl, cl); /* calls kobject_get for fw_cl on success */ + kobject_put(&fw_cl->kobj); + if (ret) { + cl_err(idev, cl, "FW client is already connected ret = %d\n", ret); + return ret; + } + + cl->fw_cl = fw_cl; + cl->state = ISSEI_HOST_CL_STATE_CONNECTED; + + *mtu = fw_cl->mtu; + *ver = fw_cl->ver; + *flags = fw_cl->flags; + cl_dbg(idev, cl, "Connected\n"); + return 0; +} + +/** + * issei_cl_disconnect - disconnect between FW and host client + * @cl: host client + * + * Return: 0 on success, -ENOTCONN if not connected + */ +int issei_cl_disconnect(struct issei_host_client *cl) +{ + struct issei_device *idev = cl->idev; + + guard(mutex)(&idev->client_lock); + + if (cl->state != ISSEI_HOST_CL_STATE_CONNECTED) + return -ENOTCONN; + __issei_cl_disconnect(idev, cl); + return 0; +} + +/** + * issei_cl_all_disconnect - disconnect all FW clients + * @idev: issei device + */ +void issei_cl_all_disconnect(struct issei_device *idev) +{ + struct issei_host_client *cl; + + guard(mutex)(&idev->client_lock); + + list_for_each_entry(cl, &idev->host_client_list, list) + __issei_cl_disconnect(idev, cl); +} + +/** + * issei_cl_write - enqueue write request + * @cl: host client + * @buf: buffer to write + * @buf_size: buffer size + * + * Add write request to the write queue and wakes working thread. + * This call takes ownership of buf memory, if succeeded. + * + * Return: size of data on success, <0 on error + */ +ssize_t issei_cl_write(struct issei_host_client *cl, const u8 *buf, size_t buf_size) +{ + struct issei_device *idev = cl->idev; + struct issei_write_buf *wbuf; + + guard(mutex)(&idev->client_lock); + + if (cl->state != ISSEI_HOST_CL_STATE_CONNECTED) + return -ENOTCONN; + + if (cl->write_in_progress) { + cl_dbg(idev, cl, "Another write is in progress\n"); + return -EAGAIN; + } + + if (buf_size > cl->fw_cl->mtu) { + cl_err(idev, cl, "Write is too big %zu > %u\n", buf_size, cl->fw_cl->mtu); + return -EFBIG; + } + + wbuf = kmalloc_obj(*wbuf); + if (!wbuf) + return -ENOMEM; + wbuf->cl = cl; + wbuf->data = buf; + wbuf->data_size = buf_size; + list_add_tail(&wbuf->list, &idev->write_queue); + cl->write_in_progress = true; + cl_dbg(idev, cl, "Write queued %zu bytes\n", buf_size); + + issei_poke_process_thread(idev); + + return buf_size; +} + +/** + * issei_cl_write_from_queue - writes first request from queue to firmware + * @idev: issei device + * + * Tries to write first request from the write queue to firmware. + * Releases buf memory, if succeeded. + * + * Return: 0 on success, <0 on error + */ +int issei_cl_write_from_queue(struct issei_device *idev) +{ + struct issei_write_buf *wbuf; + struct issei_dma_data data; + struct issei_host_client *cl; + int ret; + + guard(mutex)(&idev->client_lock); + + wbuf = list_first_entry_or_null(&idev->write_queue, struct issei_write_buf, list); + if (!wbuf) + return 0; + + cl = wbuf->cl; + + data.fw_id = cl->fw_cl->id; + data.host_id = cl->id; + data.flags = 0; + data.status = 0; + data.length = wbuf->data_size; + data.buf = (void *)wbuf->data; + ret = issei_dma_write(idev, &data); + if (ret == -EBUSY) + return 0; + if (ret == -EIO) + return ret; + if (ret >= 0) + idev->ops->irq_write_generate(idev); + cl->write_in_progress = false; + /* synchronized under host client mutex */ + if (waitqueue_active(&cl->write_wait)) + wake_up_interruptible(&cl->write_wait); + cl_dbg(idev, cl, "Write %zu bytes\n", wbuf->data_size); + __issei_cl_clean_wbuf(wbuf); + return 0; +} + +static struct issei_host_client *__issei_cl_read_buf_check(struct issei_device *idev, u16 fw_id, + u16 host_id, size_t buf_size) +{ + struct issei_host_client *cl; + + cl = __issei_cl_by_id(idev, host_id); + if (!cl) { + dev_dbg(&idev->dev, "No client %u\n", host_id); + return ERR_PTR(-ENOTTY); + } + + if (cl->state != ISSEI_HOST_CL_STATE_CONNECTED) { + cl_dbg(idev, cl, "Not connected\n"); + return ERR_PTR(-ENODEV); + } + if (cl->fw_cl->id != fw_id) { + cl_dbg(idev, cl, "Wrong firmware client %u ?= %u\n", cl->fw_cl->id, fw_id); + return ERR_PTR(-ENODEV); + } + + if (buf_size > cl->fw_cl->mtu) { + cl_err(idev, cl, "Read is too big %zu > %u\n", buf_size, cl->fw_cl->mtu); + __issei_cl_disconnect(idev, cl); + return NULL; + } + + if (cl->read_data) { + cl_err(idev, cl, "Previous data was not read by user-space, disconnecting\n"); + __issei_cl_disconnect(idev, cl); + return NULL; + } + + return cl; +} + +/** + * issei_cl_read_buf - process data from firmware + * @idev: issei device + * @fw_id: firmware client id + * @host_id: host client id + * @buf: buffer with data + * @buf_size: buffer size + * + * Puts data from firmware into provided host client storage. + * Free buffer or consume it. + * + * Return: 0 on success or recoverable error, <0 on unrecoverable error + */ +int issei_cl_read_buf(struct issei_device *idev, u16 fw_id, u16 host_id, u8 *buf, size_t buf_size) +{ + struct issei_host_client *cl; + + guard(mutex)(&idev->client_lock); + + cl = __issei_cl_read_buf_check(idev, fw_id, host_id, buf_size); + if (IS_ERR_OR_NULL(cl)) { + kfree(buf); + return PTR_ERR(cl); + } + + cl->read_data = buf; + cl->read_data_size = buf_size; + + /* synchronized under host client mutex */ + if (waitqueue_active(&cl->read_wait)) + wake_up_interruptible(&cl->read_wait); + cl_dbg(idev, cl, "Read %zu bytes\n", buf_size); + + return 0; +} + +/** + * issei_cl_read - read data from queue to provided buffer + * @cl: host client + * @buf: buffer to store data + * @buf_size: buffer size + * + * Tries to take data buffer from client and return it to caller. + * The caller receives ownership of the data buffer. + * + * Return: read data size on success, <0 on error + */ +ssize_t issei_cl_read(struct issei_host_client *cl, u8 **buf, size_t buf_size) +{ + struct issei_device *idev = cl->idev; + size_t read_data_size; + + guard(mutex)(&idev->client_lock); + + if (cl->state != ISSEI_HOST_CL_STATE_CONNECTED) + return -ENOTCONN; + + if (!cl->read_data) + return -ENOENT; + + if (cl->read_data_size > buf_size) { + cl_err(idev, cl, "Buffer is too small %zu > %zu\n", + cl->read_data_size, buf_size); + return -EFBIG; + } + + *buf = cl->read_data; + read_data_size = cl->read_data_size; + cl_dbg(idev, cl, "Read by client %zu bytes\n", read_data_size); + __issei_cl_release_rbuf(cl); + return read_data_size; +} + +/** + * issei_cl_check_read - check if client has data to read + * + * @cl: host client + * + * Return: 1 - data available, 0 - no data, < 0 on error + */ +int issei_cl_check_read(struct issei_host_client *cl) +{ + struct issei_device *idev = cl->idev; + + guard(mutex)(&idev->client_lock); + + if (cl->state != ISSEI_HOST_CL_STATE_CONNECTED) + return -ENOTCONN; + if (!cl->read_data) + return 0; + return 1; +} + +/** + * issei_cl_check_write - check if client is ready to write + * + * @cl: host client + * + * Return: 1 - can not write, 0 - can write, < 0 on error + */ +int issei_cl_check_write(struct issei_host_client *cl) +{ + struct issei_device *idev = cl->idev; + + guard(mutex)(&idev->client_lock); + + if (cl->state != ISSEI_HOST_CL_STATE_CONNECTED) + return -ENOTCONN; + if (cl->write_in_progress) + return 1; + return 0; +} + +void issei_cl_clean_all_wbuf(struct issei_host_client *cl) +{ + struct issei_device *idev = cl->idev; + + guard(mutex)(&idev->client_lock); + + __issei_cl_clean_all_wbuf(idev, cl); +} diff --git a/drivers/misc/issei/host_client.h b/drivers/misc/issei/host_client.h new file mode 100644 index 000000000000..05e2f4ade9c3 --- /dev/null +++ b/drivers/misc/issei/host_client.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2023-2026 Intel Corporation */ +#ifndef _ISSEI_HOST_CLIENT_H_ +#define _ISSEI_HOST_CLIENT_H_ + +#include +#include +#include + +struct file; + +struct issei_device; +struct issei_fw_client; + +/** + * enum issei_host_client_state - host client states + * @ISSEI_HOST_CL_STATE_DISCONNECTED: host client is disconnected + * @ISSEI_HOST_CL_STATE_CONNECTED: host client is connected + */ +enum issei_host_client_state { + ISSEI_HOST_CL_STATE_DISCONNECTED, + ISSEI_HOST_CL_STATE_CONNECTED, +}; + +/** + * struct issei_host_client - represents host client + * @list: link in host clients list + * @idev: issei parent device + * @id: host client id + * @fp: file associated with client + * + * @write_wait: waitqueue for pending write data + * @write_in_progress: indicator for write in process + * + * @state: host client state + * @fw_cl: pointer to firmware client, if connected + * + * @read_wait: waitqueue for read object + * @read_data: received data pointer + * @read_data_size: received data size + */ +struct issei_host_client { + struct list_head list; + struct issei_device *idev; + u16 id; + const struct file *fp; + + wait_queue_head_t write_wait; + bool write_in_progress; + + enum issei_host_client_state state; + struct issei_fw_client *fw_cl; + + wait_queue_head_t read_wait; + u8 *read_data; + size_t read_data_size; +}; + +struct issei_host_client *issei_cl_create(struct issei_device *idev, struct file *fp); +void issei_cl_remove(struct issei_host_client *cl); + +int issei_cl_connect(struct issei_host_client *cl, const uuid_t *uuid, u32 *mtu, u8 *ver, + u32 *flags); +int issei_cl_disconnect(struct issei_host_client *cl); +void issei_cl_all_disconnect(struct issei_device *idev); + +ssize_t issei_cl_write(struct issei_host_client *cl, const u8 *buf, size_t buf_size); +int issei_cl_write_from_queue(struct issei_device *idev); +int issei_cl_read_buf(struct issei_device *idev, u16 fw_id, u16 host_id, u8 *buf, size_t buf_size); +ssize_t issei_cl_read(struct issei_host_client *cl, u8 **buf, size_t buf_size); +int issei_cl_check_read(struct issei_host_client *cl); +int issei_cl_check_write(struct issei_host_client *cl); +void issei_cl_clean_all_wbuf(struct issei_host_client *cl); + +#endif /* ISSEI_HOST_CLIENT_H_ */ From 7bd4b9991db20b0df5a8dfbef05920eabd40de28 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Wed, 13 May 2026 17:18:44 +0300 Subject: [PATCH 087/135] issei: implement main thread and ham messages Introduce the main thread and HECI Active Management (HAM) message handling for the ISSEI (Intel Silicon Security Engine Interface) subsystem. The main thread is responsible for managing the reset flow and processing messages, while the HAM message handling is crucial for initializing communication with the firmware and managing clients. With this implementation, the ISSEI driver is capable of performing the required initialization and management of communication between the host and the firmware. Reviewed-by: Karol Wachowski Co-developed-by: Vitaly Lubart Signed-off-by: Vitaly Lubart Signed-off-by: Alexander Usyskin Link: https://patch.msgid.link/20260513-issei-for-upstream-v1-3-f590038678f9@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/issei/Makefile | 2 + drivers/misc/issei/ham.c | 163 ++++++++++++++++++ drivers/misc/issei/ham.h | 20 +++ drivers/misc/issei/issei_dev.h | 3 + drivers/misc/issei/main.c | 296 +++++++++++++++++++++++++++++++++ 5 files changed, 484 insertions(+) create mode 100644 drivers/misc/issei/ham.c create mode 100644 drivers/misc/issei/ham.h create mode 100644 drivers/misc/issei/main.c diff --git a/drivers/misc/issei/Makefile b/drivers/misc/issei/Makefile index 1471ed99d619..f21e0b985c94 100644 --- a/drivers/misc/issei/Makefile +++ b/drivers/misc/issei/Makefile @@ -7,3 +7,5 @@ issei-objs += cdev.o issei-objs += dma.o issei-objs += fw_client.o issei-objs += host_client.o +issei-objs += ham.o +issei-objs += main.o diff --git a/drivers/misc/issei/ham.c b/drivers/misc/issei/ham.c new file mode 100644 index 000000000000..17eae91f077d --- /dev/null +++ b/drivers/misc/issei/ham.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2023-2026 Intel Corporation */ +#include +#include +#include +#include + +#include "dma.h" +#include "fw_client.h" +#include "ham.h" +#include "hw_msg.h" +#include "issei_dev.h" + +static int __issei_ham_send_msg(struct issei_device *idev, u32 length, void *buf) +{ + struct issei_dma_data data = { }; + int ret; + + data.length = length; + data.buf = buf; + ret = issei_dma_write(idev, &data); + if (ret) + return ret; + return idev->ops->irq_write_generate(idev); +} + +/** + * issei_ham_send_start_req - send start request to firmware + * @idev: issei device object + * + * Return: 0 on success, <0 on failures + */ +int issei_ham_send_start_req(struct issei_device *idev) +{ + struct ham_start_message_req req; + + req.header.cmd = HAM_BUS_CMD_START_REQ; + req.supported_version = ISSEI_SUPPORTED_PROTOCOL_VER; + req.heci_capabilities_length = 0; + + return __issei_ham_send_msg(idev, sizeof(req), &req); +} + +/** + * issei_ham_send_clients_req - send clients request to firmware + * @idev: issei device object + * + * Return: 0 on success, <0 on failures + */ +int issei_ham_send_clients_req(struct issei_device *idev) +{ + struct ham_get_clients_req req; + + req.header.cmd = HAM_BUS_CMD_CLIENT_REQ; + + return __issei_ham_send_msg(idev, sizeof(req), &req); +} + +static int issei_ham_start_rsp(struct issei_device *idev, const u8 *buf, size_t length) +{ + struct ham_start_message_res *res = (struct ham_start_message_res *)buf; + int ret; + + if (idev->rst_state != ISSEI_RST_STATE_START) { + dev_err(&idev->dev, "Wrong state %d != %d\n", + idev->rst_state, ISSEI_RST_STATE_START); + return -EPROTO; + } + + if (length < sizeof(*res)) { + dev_err(&idev->dev, "Small start response size %zu < %zu\n", + length, sizeof(*res)); + return -EPROTO; + } + + if (length - sizeof(*res) != res->heci_capabilities_length) { + dev_err(&idev->dev, "Wrong start response size %zu != %u\n", + length - sizeof(*res), res->heci_capabilities_length); + return -EPROTO; + } + + memcpy(idev->fw_version, res->fw_version, sizeof(idev->fw_version)); + idev->fw_protocol_ver = res->supported_version; + dev_dbg(&idev->dev, "FW protocol: %u FW version %u.%u.%u.%u", idev->fw_protocol_ver, + idev->fw_version[0], idev->fw_version[1], + idev->fw_version[2], idev->fw_version[3]); + + ret = issei_ham_send_clients_req(idev); + if (ret == -EBUSY) + ret = 0; + + return ret; +} + +static int issei_ham_client_rsp(struct issei_device *idev, const u8 *buf, size_t length) +{ + struct ham_get_clients_res *res = (struct ham_get_clients_res *)buf; + struct ham_client_properties *client; + + if (idev->rst_state != ISSEI_RST_STATE_CLIENT_ENUM) { + dev_err(&idev->dev, "Wrong state %d != %d\n", + idev->rst_state, ISSEI_RST_STATE_CLIENT_ENUM); + return -EPROTO; + } + + if (length < sizeof(*res)) { + dev_err(&idev->dev, "Small response size %zu < %zu\n", length, sizeof(*res)); + return -EPROTO; + } + + if (length - sizeof(*res) != res->client_count * sizeof(struct ham_client_properties)) { + dev_err(&idev->dev, "Wrong response size %zu < %zu\n", + length - sizeof(*res), + res->client_count * sizeof(struct ham_client_properties)); + return -EPROTO; + } + + guard(mutex)(&idev->client_lock); + + for (size_t i = 0; i < res->client_count; i++) { + client = &res->clients_props[i]; + dev_dbg(&idev->dev, "client: id = %u ver = %u uuid = %pUb mtu = %u flags = %u", + client->client_number, client->protocol_ver, &client->client_uuid, + client->client_mtu, client->flags); + issei_fw_cl_create(idev, client->client_number, client->protocol_ver, + &client->client_uuid, client->client_mtu, client->flags); + } + return 0; +} + +static int __issei_ham_process_ham_rsp(struct issei_device *idev, const u8 *buf, size_t length) +{ + struct ham_bus_message *hdr = (struct ham_bus_message *)buf; + + switch (hdr->cmd) { + case HAM_BUS_CMD_START_RSP: + return issei_ham_start_rsp(idev, buf, length); + + case HAM_BUS_CMD_CLIENT_RSP: + return issei_ham_client_rsp(idev, buf, length); + + default: + dev_err(&idev->dev, "Unexpected command 0x%x", hdr->cmd); + return -EPROTO; + } +} + +/** + * issei_ham_process_ham_rsp - process response from firmware and release buffer + * @idev: issei device object + * @buf: response buffer + * @length: response buffer length + * + * Return: 0 on success, <0 on failures + */ +int issei_ham_process_ham_rsp(struct issei_device *idev, const u8 *buf, size_t length) +{ + int ret; + + ret = __issei_ham_process_ham_rsp(idev, buf, length); + kfree(buf); + return ret; +} diff --git a/drivers/misc/issei/ham.h b/drivers/misc/issei/ham.h new file mode 100644 index 000000000000..37ea64bb2920 --- /dev/null +++ b/drivers/misc/issei/ham.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2023-2026 Intel Corporation */ +#ifndef _ISSEI_HAM_H_ +#define _ISSEI_HAM_H_ + +#include + +struct issei_device; + +int issei_ham_send_start_req(struct issei_device *idev); +int issei_ham_send_clients_req(struct issei_device *idev); + +static inline bool issei_is_ham_rsp(u16 fw_id, u16 host_id) +{ + return fw_id == 0 && host_id == 0; +} + +int issei_ham_process_ham_rsp(struct issei_device *idev, const u8 *buf, size_t length); + +#endif /* _ISSEI_HAM_H_ */ diff --git a/drivers/misc/issei/issei_dev.h b/drivers/misc/issei/issei_dev.h index c742e7fe6cb6..b47c4a7c2da4 100644 --- a/drivers/misc/issei/issei_dev.h +++ b/drivers/misc/issei/issei_dev.h @@ -157,4 +157,7 @@ static inline void issei_poke_process_thread(struct issei_device *idev) WRITE_ONCE(idev->has_data, true); wake_up_interruptible(&idev->wait_has_data); } + +int issei_start(struct issei_device *idev); +void issei_stop(struct issei_device *idev); #endif /* _ISSEI_DEV_H_ */ diff --git a/drivers/misc/issei/main.c b/drivers/misc/issei/main.c new file mode 100644 index 000000000000..5987fb340250 --- /dev/null +++ b/drivers/misc/issei/main.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2023-2026 Intel Corporation */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cdev.h" +#include "fw_client.h" +#include "host_client.h" +#include "ham.h" +#include "issei_dev.h" + +static void issei_rst_state_set(struct issei_device *idev, enum issei_rst_state state) +{ + idev->rst_state = state; + /* wake up the thread */ + if (waitqueue_active(&idev->wait_rst_state)) + wake_up(&idev->wait_rst_state); +} + +static int issei_reset(struct issei_device *idev) +{ + int ret; + + idev->ops->irq_clear(idev); + + issei_cl_all_disconnect(idev); + issei_fw_cl_remove_all(idev); + /* No need to check for overflow here, the counter is used only for info */ + idev->all_reset_count++; + ret = idev->ops->hw_reset(idev, !idev->power_down); + issei_dmam_setup(idev); + if (ret) { + dev_err(&idev->dev, "hw_reset failed ret = %d\n", ret); + return ret; + } + + if (idev->power_down) { + dev_dbg(&idev->dev, "powering down: end of reset\n"); + issei_rst_state_set(idev, ISSEI_RST_STATE_DISABLED); + return -ENODEV; + } + return 0; +} + +static int issei_process_read_msg(struct issei_device *idev) +{ + struct issei_dma_data data = {}; + int ret; + + ret = issei_dma_read(idev, &data); + if (ret) + return ret; + + dev_dbg(&idev->dev, "Processing response %u %u %u %u\n", data.fw_id, data.host_id, + data.status, data.length); + if (data.status != HAMS_SUCCESS) { + dev_err(&idev->dev, "Command failed with status 0x%02X", data.status); + kfree(data.buf); + ret = -EIO; + } else { + if (issei_is_ham_rsp(data.fw_id, data.host_id)) + ret = issei_ham_process_ham_rsp(idev, data.buf, data.length); + else + ret = issei_cl_read_buf(idev, data.fw_id, data.host_id, + data.buf, data.length); + } + idev->ops->irq_write_generate(idev); + return ret; +} + +static int issei_process_write_msg(struct issei_device *idev) +{ + if (idev->rst_state != ISSEI_RST_STATE_DONE) + return 0; + + return issei_cl_write_from_queue(idev); +} + +static int issei_process_thread(void *_dev) +{ + long timeout, old_timeout = MAX_SCHEDULE_TIMEOUT; + struct issei_device *idev = _dev; + int ret; + + while (!kthread_should_stop()) { + dev_dbg(&idev->dev, "process_work in %d\n", idev->rst_state); + if (!idev->ops->hw_is_ready(idev) && idev->rst_state > ISSEI_RST_STATE_HW_READY) { + if (!idev->power_down) + dev_dbg(&idev->dev, "HW not ready, resetting\n"); + idev->rst_state = ISSEI_RST_STATE_INIT; + } + if (idev->power_down) + idev->rst_state = ISSEI_RST_STATE_INIT; + WRITE_ONCE(idev->has_data, false); + dev_dbg(&idev->dev, "reset_step in %d\n", idev->rst_state); + timeout = MAX_SCHEDULE_TIMEOUT; + ret = 0; + switch (idev->rst_state) { + case ISSEI_RST_STATE_DISABLED: + if (idev->power_down) { + dev_dbg(&idev->dev, "Interrupt in power down?\n"); + break; + } + idev->rst_state = ISSEI_RST_STATE_INIT; + fallthrough; + + case ISSEI_RST_STATE_INIT: + idev->ops->irq_clear(idev); + idev->ops->irq_sync(idev); + + if (!idev->power_down) { + idev->reset_count++; + if (idev->reset_count > ISSEI_MAX_CONSEC_RESET) { + dev_err(&idev->dev, "reset: reached maximal consecutive resets: disabling the device\n"); + issei_rst_state_set(idev, ISSEI_RST_STATE_DISABLED); + break; + } + } + + ret = issei_reset(idev); + if (idev->power_down) { + dev_dbg(&idev->dev, "Powering down\n"); + return 0; + } + if (ret) + break; + + idev->rst_state = ISSEI_RST_STATE_HW_READY; + timeout = msecs_to_jiffies(ISSEI_RST_HW_READY_TIMEOUT_MSEC); + break; + + case ISSEI_RST_STATE_HW_READY: + if (!idev->ops->hw_is_ready(idev)) { + dev_dbg(&idev->dev, "HW is not ready?\n"); + timeout = old_timeout; + break; + } + + dev_dbg(&idev->dev, "HW is ready\n"); + idev->ops->hw_reset_release(idev); + idev->ops->host_set_ready(idev); + ret = idev->ops->setup_message_send(idev); + if (ret) + break; + + idev->rst_state = ISSEI_RST_STATE_SETUP; + timeout = msecs_to_jiffies(ISSEI_RST_STEP_TIMEOUT_MSEC); + break; + + case ISSEI_RST_STATE_SETUP: + ret = idev->ops->setup_message_recv(idev); + if (ret) { + if (ret == -ENODATA) { + ret = 0; + timeout = old_timeout; + } + } else { + timeout = msecs_to_jiffies(ISSEI_RST_STEP_TIMEOUT_MSEC); + ret = issei_ham_send_start_req(idev); + idev->rst_state = ISSEI_RST_STATE_START; + } + break; + + case ISSEI_RST_STATE_START: + ret = issei_process_read_msg(idev); + if (!ret) { + timeout = msecs_to_jiffies(ISSEI_RST_STEP_TIMEOUT_MSEC); + idev->rst_state = ISSEI_RST_STATE_CLIENT_ENUM; + } else if (ret == -ENODATA) { + ret = 0; + timeout = old_timeout; + } + break; + + case ISSEI_RST_STATE_CLIENT_ENUM: + ret = issei_process_read_msg(idev); + if (ret) { + if (ret == -ENODATA) { + ret = 0; + timeout = old_timeout; + } + } else { + idev->reset_count = 0; + idev->rst_state = ISSEI_RST_STATE_DONE; + dev_dbg(&idev->dev, "Reset finished successfully\n"); + } + break; + + case ISSEI_RST_STATE_DONE: + ret = issei_process_read_msg(idev); + if (ret != 0 && ret != -ENODATA) + break; + + ret = issei_process_write_msg(idev); + break; + } + + if (ret) { + dev_warn(&idev->dev, "Process failed ret = %d\n", ret); + idev->rst_state = ISSEI_RST_STATE_INIT; + continue; + } + + /* + * Every thread that has data to process sets the 'has_data' flag and + * triggers the wait queue. + * The processing thread, in each loop iteration, resets 'has_data' + * and processes all available data. + * + * After processing, the thread waits for 'has_data' to be set again. + * + * If the wait function times out but 'has_data' becomes 1 before + * the subsequent atomic read check, this is acceptable from a flow + * perspective - the thread will continue processing the data. + * + * The 'has_data' flag cannot become 0 between the wait function and + * the atomic read check, since only this thread is allowed to reset it to 0. + */ + + old_timeout = wait_event_interruptible_timeout(idev->wait_has_data, + READ_ONCE(idev->has_data), + timeout); + if (idev->rst_state == ISSEI_RST_STATE_DISABLED) + continue; + + if (!READ_ONCE(idev->has_data)) { + dev_warn(&idev->dev, "Timed out at state %d, resetting\n", + idev->rst_state); + idev->rst_state = ISSEI_RST_STATE_INIT; + } + } + + return 0; +} + +/** + * issei_start - configure HW device and start processing thread. + * @idev: the device structure + * + * Return: 0 on success, < 0 on failure + */ +int issei_start(struct issei_device *idev) +{ + int ret; + + idev->power_down = false; + + ret = issei_dmam_setup(idev); + if (ret) + return ret; + + idev->ops->irq_clear(idev); + + ret = idev->ops->hw_config(idev); + if (ret) + return ret; + + idev->process_thread = kthread_run(issei_process_thread, idev, + "kisseiprocess/%s", dev_name(&idev->dev)); + if (IS_ERR(idev->process_thread)) { + ret = PTR_ERR(idev->process_thread); + dev_err(&idev->dev, "unable to create process thread. ret = %d\n", ret); + return ret; + } + + issei_poke_process_thread(idev); + return 0; +} +EXPORT_SYMBOL_GPL(issei_start); + +/** + * issei_stop - stop interrupts and processing thread. + * @idev: the device structure + */ +void issei_stop(struct issei_device *idev) +{ + idev->power_down = true; + + idev->ops->irq_clear(idev); + idev->ops->irq_sync(idev); + + issei_poke_process_thread(idev); + + wait_event_timeout(idev->wait_rst_state, + (idev->rst_state == ISSEI_RST_STATE_DISABLED), + msecs_to_jiffies(ISSEI_STOP_TIMEOUT_MSEC)); + kthread_stop(idev->process_thread); +} +EXPORT_SYMBOL_GPL(issei_stop); From 8bf5e84998c3fd00ca9e29a8a8143d23ea1e8663 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Wed, 13 May 2026 17:18:45 +0300 Subject: [PATCH 088/135] issei: add heci hardware module Add support for the ISSEI (Intel Silicon Security Engine Interface) HECI PCI devices. Add the necessary PCI handling routines, hardware definitions, register mappings and hardware access routines. This enables the communication via HECI PCI device advertized by BIOS. Reviewed-by: Karol Wachowski Co-developed-by: Vitaly Lubart Signed-off-by: Vitaly Lubart Signed-off-by: Alexander Usyskin Link: https://patch.msgid.link/20260513-issei-for-upstream-v1-4-f590038678f9@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/issei/Kconfig | 16 + drivers/misc/issei/Makefile | 4 + drivers/misc/issei/hw_heci.c | 550 ++++++++++++++++++++++++++++++ drivers/misc/issei/hw_heci.h | 47 +++ drivers/misc/issei/hw_heci_regs.h | 35 ++ drivers/misc/issei/pci_heci.c | 151 ++++++++ 6 files changed, 803 insertions(+) create mode 100644 drivers/misc/issei/hw_heci.c create mode 100644 drivers/misc/issei/hw_heci.h create mode 100644 drivers/misc/issei/hw_heci_regs.h create mode 100644 drivers/misc/issei/pci_heci.c diff --git a/drivers/misc/issei/Kconfig b/drivers/misc/issei/Kconfig index d98ac7925ce6..12a3fd809969 100644 --- a/drivers/misc/issei/Kconfig +++ b/drivers/misc/issei/Kconfig @@ -11,3 +11,19 @@ config INTEL_SSEI If selected, the /dev/isseiX device will be created. If in doubt, select N. + +if INTEL_SSEI + +config INTEL_SSEI_HW_HECI + tristate "Intel Silicon Security Engine Interface Hardware" + depends on X86 && PCI + help + HECI interface of communication channel between + the host and the Silicon Security Engine. + + Implementation of ISSEI communication channel over + the HECI hardware PCI device. + This device is available on Intel client CPUs released in 2024 + (Lunar Lake) or later. + +endif diff --git a/drivers/misc/issei/Makefile b/drivers/misc/issei/Makefile index f21e0b985c94..8fac360c3342 100644 --- a/drivers/misc/issei/Makefile +++ b/drivers/misc/issei/Makefile @@ -9,3 +9,7 @@ issei-objs += fw_client.o issei-objs += host_client.o issei-objs += ham.o issei-objs += main.o + +obj-$(CONFIG_INTEL_SSEI_HW_HECI) += issei-heci.o +issei-heci-objs := pci_heci.o +issei-heci-objs += hw_heci.o diff --git a/drivers/misc/issei/hw_heci.c b/drivers/misc/issei/hw_heci.c new file mode 100644 index 000000000000..35c55de5c67a --- /dev/null +++ b/drivers/misc/issei/hw_heci.c @@ -0,0 +1,550 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2023-2026 Intel Corporation */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hw_heci.h" +#include "hw_heci_regs.h" +#include "hw_msg.h" + +/** + * heci_reg_read - Reads 32bit data from the issei heci device + * @hw: the heci hardware structure + * @offset: offset from which to read the data + * + * Return: register value (u32) + */ +static inline u32 heci_reg_read(const struct issei_heci_hw *hw, unsigned long offset) +{ + return ioread32(hw->mem_addr + offset); +} + +/** + * heci_reg_write - Writes 32bit data to the issei heci device + * + * @hw: the heci hardware structure + * @offset: offset from which to write the data + * @value: register value to write (u32) + */ +static inline void heci_reg_write(const struct issei_heci_hw *hw, unsigned long offset, u32 value) +{ + iowrite32(value, hw->mem_addr + offset); +} + +/** + * heci_fwcbrw_read - Reads 32bit data from heci circular buffer + * @hw: the heci hardware structure + * + * Return: FW_CB_RW register value (u32) + */ +static inline u32 heci_fwcbrw_read(const struct issei_heci_hw *hw) +{ + return heci_reg_read(hw, FW_CB_RW); +} + +/** + * heci_hcbww_write - write 32bit data to the host circular buffer + * @hw: the heci hardware structure + * @data: 32bit data to be written to the host circular buffer + */ +static inline void heci_hcbww_write(const struct issei_heci_hw *hw, u32 data) +{ + heci_reg_write(hw, H_CB_WW, data); +} + +/** + * heci_irq_src - Filters IRQ source bits from the host CSR + * @hcsr: host CSR register value + * + * Return: interrupt source bits of host CSR + */ +static inline u32 heci_irq_src(u32 hcsr) +{ + return hcsr & H_CSR_IS; +} + +/** + * heci_hcsr_read - Reads 32bit data from the host CSR + * @idev: the device structure + * + * Return: H_CSR register value (u32) + */ +static inline u32 heci_hcsr_read(const struct issei_device *idev) +{ + return heci_reg_read(to_heci_hw(idev), H_CSR); +} + +/** + * heci_hcsr_write - writes H_CSR register to device + * @idev: the device structure + * @reg: new register value + */ +static inline void heci_hcsr_write(struct issei_device *idev, u32 reg) +{ + heci_reg_write(to_heci_hw(idev), H_CSR, reg); +} + +/** + * heci_hcsr_set - writes H_CSR register to the heci device + * @idev: the device structure + * @reg: new register value + * + * Writes H_CSR register to the heci device + * and ignores the H_IS bit for it is write-one-to-zero. + * + */ +static inline void heci_hcsr_set(struct issei_device *idev, u32 reg) +{ + reg &= ~H_CSR_IS; + heci_hcsr_write(idev, reg); +} + +/** + * heci_hcsr_set_hig - set host interrupt (set H_CSR_IG) + * @idev: the device structure + */ +static inline void heci_hcsr_set_hig(struct issei_device *idev) +{ + u32 reg; + + reg = heci_hcsr_read(idev) | H_CSR_IG; + heci_hcsr_set(idev, reg); +} + +/** + * heci_fwcsr_read - Reads 32bit data from the FW CSR + * @idev: the device structure + * + * Return: FW_CSR_HA register value (u32) + */ +static inline u32 heci_fwcsr_read(const struct issei_device *idev) +{ + return heci_reg_read(to_heci_hw(idev), FW_CSR_HA); +} + +/** + * heci_count_full_read_slots - counts read full slots. + * @idev: the device structure + * + * Return: -EOVERFLOW if overflow, otherwise filled slots count + */ +static int heci_count_full_read_slots(struct issei_device *idev) +{ + u8 buffer_depth, filled_slots; + u8 read_ptr, write_ptr; + u32 reg; + + reg = heci_fwcsr_read(idev); + buffer_depth = (u8)FIELD_GET(FW_CSR_CBD, reg); + read_ptr = (u8)FIELD_GET(FW_CSR_CBRP, reg); + write_ptr = (u8)FIELD_GET(FW_CSR_CBWP, reg); + filled_slots = write_ptr - read_ptr; + + /* check for overflow */ + if (filled_slots > buffer_depth) + return -EOVERFLOW; + + dev_dbg(&idev->dev, "filled_slots = %08x\n", filled_slots); + return filled_slots; +} + +/** + * heci_irq_disable - disables heci device interrupts + * @idev: the device structure + * @reg: supplied hcsr register value + * + * disables heci device interrupts using supplied hcsr register value. + */ +static inline void heci_irq_disable(struct issei_device *idev, u32 reg) +{ + reg &= ~H_CSR_IE; + heci_hcsr_set(idev, reg); +} + +/** + * heci_irq_clear - clear and stop interrupts + * @idev: the device structure + * @reg: supplied hcsr register value + */ +static inline void heci_irq_clear(struct issei_device *idev, u32 reg) +{ + if (heci_irq_src(reg)) + heci_hcsr_write(idev, reg); +} + +/** + * issei_heci_irq_clear - clear and stop interrupts + * @idev: the device structure + */ +static void issei_heci_irq_clear(struct issei_device *idev) +{ + u32 reg = heci_hcsr_read(idev); + + heci_irq_clear(idev, reg); +} + +/** + * issei_heci_irq_enable - enables heci device interrupts + * @idev: the device structure + */ +static void issei_heci_irq_enable(struct issei_device *idev) +{ + u32 reg; + + reg = heci_hcsr_read(idev) | H_CSR_IE; + heci_hcsr_set(idev, reg); +} + +/** + * issei_heci_irq_disable - disables heci device interrupts + * @idev: the device structure + */ +static void issei_heci_irq_disable(struct issei_device *idev) +{ + u32 reg = heci_hcsr_read(idev); + + heci_irq_disable(idev, reg); +} + +static void issei_heci_irq_sync(struct issei_device *idev) +{ + synchronize_irq(to_heci_hw(idev)->irq); +} + +/** + * issei_heci_hw_reset_release - release device from the reset + * @idev: the device structure + */ +static void issei_heci_hw_reset_release(struct issei_device *idev) +{ + u32 reg = heci_hcsr_read(idev); + + reg |= H_CSR_IG; + reg &= ~H_CSR_RST; + heci_hcsr_set(idev, reg); +} + +/** + * heci_hw_is_ready - check whether the hw has turned ready + * @idev: the device structure + * + * Return: bool + */ +static bool heci_hw_is_ready(struct issei_device *idev) +{ + u32 reg = heci_fwcsr_read(idev); + + return reg & FW_CSR_RDY; +} + +/** + * heci_hw_is_resetting - check whether the hw is in reset + * @idev: the device structure + * + * Return: bool + */ +static bool heci_hw_is_resetting(struct issei_device *idev) +{ + u32 reg = heci_fwcsr_read(idev); + + return reg & FW_CSR_RST; +} + +/** + * issei_heci_host_set_ready - enable device + * @idev: the device structure + */ +static void issei_heci_host_set_ready(struct issei_device *idev) +{ + u32 reg = heci_hcsr_read(idev); + + reg |= H_CSR_IE | H_CSR_IG | H_CSR_RDY; + heci_hcsr_set(idev, reg); +} + +/** + * issei_heci_hw_reset - resets fw via heci csr register. + * @idev: the device structure + * @enable: if interrupt should be enabled after reset. + * + * Return: 0 on success an error code otherwise + */ +static int issei_heci_hw_reset(struct issei_device *idev, bool enable) +{ + u32 reg; + + if (enable) + issei_heci_irq_enable(idev); + + reg = heci_hcsr_read(idev); + /* + * H_CSR_RST may be found lit before reset is started, + * for example if preceding reset flow hasn't completed. + * In that case asserting H_CSR_RST will be ignored, therefore + * we need to clean H_CSR_RST bit to start a successful reset sequence. + */ + if (reg & H_CSR_RST) { + dev_warn(&idev->dev, "H_CSR_RST is set = 0x%08X", reg); + reg &= ~H_CSR_RST; + heci_hcsr_set(idev, reg); + reg = heci_hcsr_read(idev); + } + + reg |= H_CSR_RST | H_CSR_IG | H_CSR_IS; + + if (!enable) + reg &= ~H_CSR_IE; + + heci_hcsr_write(idev, reg); + + /* + * Host reads the H_CSR once to ensure that the + * posted write to H_CSR completes. + */ + reg = heci_hcsr_read(idev); + + if (!(reg & H_CSR_RST)) + dev_warn(&idev->dev, "H_CSR_RST is not set = 0x%08X", reg); + + if (reg & H_CSR_RDY) + dev_warn(&idev->dev, "H_CSR_RDY is not cleared 0x%08X", reg); + + if (!enable) + issei_heci_hw_reset_release(idev); + return 0; +} + +/** + * issei_heci_irq_write_generate - generate interrupt to signal write completion. + * @idev: the device structure + * + * Return: 0 on success, -EIO if hardware is not ready and requires reset + */ +static int issei_heci_irq_write_generate(struct issei_device *idev) +{ + struct issei_heci_hw *hw = to_heci_hw(idev); + + scoped_guard(spinlock_irqsave, &hw->access_lock) + heci_hcsr_set_hig(idev); + if (!heci_hw_is_ready(idev)) + return -EIO; + return 0; +} + +/** + * issei_heci_hw_config - initial hardware configuration. + * @idev: the device structure + * + * Return: 0 always + */ +static int issei_heci_hw_config(struct issei_device *idev) +{ + struct issei_heci_hw *hw = to_heci_hw(idev); + u32 reg; + + /* Doesn't change in runtime */ + reg = heci_hcsr_read(idev); + hw->hbuf_depth = FIELD_GET(H_CSR_CBD, reg); + + return 0; +} + +/** + * heci_write_hbuf - write to hardware buffer + * @idev: the device structure + * @data: data to write + * @data_len: data size + * + * Return: 0 on success, <0 on error + */ +static int heci_write_hbuf(struct issei_device *idev, const void *data, size_t data_len) +{ + struct issei_heci_hw *hw = to_heci_hw(idev); + + if (!IS_ALIGNED(data_len, CB_SLOT_SIZE)) { + dev_err(&idev->dev, "Data size %zu not aligned to slot size %lu\n", + data_len, CB_SLOT_SIZE); + return -EINVAL; + } + + scoped_guard(spinlock_irqsave, &hw->access_lock) { + const u32 *reg_buf = data; + size_t i; + + for (i = 0; i < data_len / CB_SLOT_SIZE; i++) + heci_hcbww_write(hw, reg_buf[i]); + } + + return issei_heci_irq_write_generate(idev); +} + +/** + * heci_read_hbuf - read data from hardware buffer + * @idev: the device structure + * @data: buffer to store read data + * @data_len: buffer size + * + * Return: 0 on success, <0 on error + */ +static int heci_read_hbuf(struct issei_device *idev, void *data, size_t data_len) +{ + struct issei_heci_hw *hw = to_heci_hw(idev); + u32 *reg_buf = data; + + if (!IS_ALIGNED(data_len, CB_SLOT_SIZE)) { + dev_err(&idev->dev, "Data size %zu not aligned to slot size %lu\n", + data_len, CB_SLOT_SIZE); + return -EINVAL; + } + + scoped_guard(spinlock_irqsave, &hw->access_lock) { + for (; data_len >= CB_SLOT_SIZE; data_len -= CB_SLOT_SIZE) + *reg_buf++ = heci_fwcbrw_read(hw); + } + return 0; +} + +/** + * issei_heci_setup_message_send - send setup message to firmware + * @idev: the device structure + * + * Return: 0 on success, <0 on error + */ +static int issei_heci_setup_message_send(struct issei_device *idev) +{ + struct ham_setup_shared_memory_req req = { + .msg_id = HAM_CB_MESSAGE_ID_REQ, + .ver = HAM_CB_MESSAGE_VER, + .reserved = 0, + .buffer_physical_address = idev->dma.daddr, + .host_to_fw_section_length = idev->dma.length.h2f, + .fw_to_host_section_length = idev->dma.length.f2h, + .control_length = idev->dma.length.ctl + }; + int ret; + + ret = heci_write_hbuf(idev, &req, sizeof(req)); + if (ret) + dev_err(&idev->dev, "Shared memory req write failed ret = %d\n", ret); + + return ret; +} + +/** + * issei_heci_setup_message_recv - receive setup message response from firmware + * @idev: the device structure + * + * Return: 0 on success, <0 on error + */ +static int issei_heci_setup_message_recv(struct issei_device *idev) +{ + struct ham_setup_shared_memory_res res; + int ret; + + if (heci_count_full_read_slots(idev) != sizeof(res) / CB_SLOT_SIZE) { + dev_dbg(&idev->dev, "Setup response is not fully received\n"); + return -ENODATA; + } + + ret = heci_read_hbuf(idev, &res, sizeof(res)); + if (ret) { + dev_err(&idev->dev, "Shared memory res read failed ret = %d\n", ret); + return ret; + } + + if (res.msg_id != HAM_CB_MESSAGE_ID_RES) { + dev_err(&idev->dev, "Shared memory res header 0x%x != 0x%x\n", + res.msg_id, HAM_CB_MESSAGE_ID_RES); + return -EPROTO; + } + if (res.status != 0) { + dev_err(&idev->dev, "Shared memory res status %d != 0\n", res.status); + return -EPROTO; + } + + return 0; +} + +static const struct issei_hw_ops hw_heci_ops = { + .irq_clear = issei_heci_irq_clear, + .irq_enable = issei_heci_irq_enable, + .irq_disable = issei_heci_irq_disable, + .irq_sync = issei_heci_irq_sync, + + .hw_reset = issei_heci_hw_reset, + .hw_config = issei_heci_hw_config, + .hw_reset_release = issei_heci_hw_reset_release, + .host_set_ready = issei_heci_host_set_ready, + + .hw_is_ready = heci_hw_is_ready, + + .setup_message_send = issei_heci_setup_message_send, + .setup_message_recv = issei_heci_setup_message_recv, + .irq_write_generate = issei_heci_irq_write_generate, +}; + +const struct issei_hw_ops *issei_heci_get_ops(void) +{ + return &hw_heci_ops; +} + +irqreturn_t issei_heci_irq_quick_handler(int irq, void *dev_id) +{ + struct issei_device *idev = dev_id; + struct issei_heci_hw *hw = to_heci_hw(idev); + u32 reg; + + reg = heci_hcsr_read(idev); + if (!heci_irq_src(reg)) + return IRQ_NONE; + + scoped_guard(spinlock_irqsave, &hw->access_lock) { + reg = heci_hcsr_read(idev); + heci_irq_clear(idev, reg); + + if (heci_hw_is_resetting(idev)) + heci_hcsr_set_hig(idev); + } + + dev_dbg(&idev->dev, "interrupt source 0x%08X\n", heci_irq_src(reg)); + + issei_poke_process_thread(idev); + + return IRQ_HANDLED; +} + +static const struct hw_heci_cfg hw_heci_pch_cfg = { + .dma_length.h2f = SZ_64K, + .dma_length.f2h = SZ_64K, + .dma_length.ctl = SZ_4K, +}; + +const struct hw_heci_cfg *issei_heci_get_cfg(kernel_ulong_t idx) +{ + return &hw_heci_pch_cfg; +} + +/** + * issei_heci_dev_init - initializes the issei device structure with hw_heci + * @idev: device structure + * @mem_addr: memory address on bar + * @cfg: per device generation config + */ +void issei_heci_dev_init(struct issei_device *idev, + void __iomem *mem_addr, const struct hw_heci_cfg *cfg) +{ + struct issei_heci_hw *hw = to_heci_hw(idev); + + spin_lock_init(&hw->access_lock); + hw->mem_addr = mem_addr; + hw->cfg = cfg; +} diff --git a/drivers/misc/issei/hw_heci.h b/drivers/misc/issei/hw_heci.h new file mode 100644 index 000000000000..0ae2faeedf12 --- /dev/null +++ b/drivers/misc/issei/hw_heci.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2023-2026 Intel Corporation */ +#ifndef _ISSEI_HW_HECI_H_ +#define _ISSEI_HW_HECI_H_ +#include +#include +#include + +#include "issei_dev.h" + +/* + * hw_heci_cfg - issei heci device configuration + * + * @dma_length: DMA area length + */ +struct hw_heci_cfg { + const struct issei_dma_length dma_length; +}; + +/** + * struct issei_heci_hw - issei heci hw specific data + * + * @cfg: per device generation config and ops + * @mem_addr: io memory address + * @irq: device irq number + * @access_lock: spinlock to protect hw access + * @hbuf_depth: depth of hardware host/write buffer in slots + */ +struct issei_heci_hw { + const struct hw_heci_cfg *cfg; + void __iomem *mem_addr; + int irq; + spinlock_t access_lock; + u8 hbuf_depth; +}; + +#define to_heci_hw(dev) ((struct issei_heci_hw *)(dev)->hw) + +const struct hw_heci_cfg *issei_heci_get_cfg(kernel_ulong_t idx); +const struct issei_hw_ops *issei_heci_get_ops(void); + +void issei_heci_dev_init(struct issei_device *idev, + void __iomem *mem_addr, const struct hw_heci_cfg *cfg); + +irqreturn_t issei_heci_irq_quick_handler(int irq, void *dev_id); + +#endif /* _ISSEI_HW_HECI_H_ */ diff --git a/drivers/misc/issei/hw_heci_regs.h b/drivers/misc/issei/hw_heci_regs.h new file mode 100644 index 000000000000..a991b670a9d3 --- /dev/null +++ b/drivers/misc/issei/hw_heci_regs.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2023-2026 Intel Corporation */ +#ifndef _ISSEI_HW_HECI_REGS_H_ +#define _ISSEI_HW_HECI_REGS_H_ + +#include + +/* H_CB_WW - Host Circular Buffer (CB) Write Window register */ +#define H_CB_WW 0x0 +/* H_CSR - Host Control Status register */ +#define H_CSR 0x4 +#define H_CSR_CBD GENMASK(31, 24) /* Host Circular Buffer Depth */ +#define H_CSR_CBWP GENMASK(23, 16) /* Host Circular Buffer Write Pointer */ +#define H_CSR_CBRP GENMASK(15, 8) /* Host Circular Buffer Read Pointer */ +#define H_CSR_RST BIT(4) /* Host Reset */ +#define H_CSR_RDY BIT(3) /* Host Ready */ +#define H_CSR_IG BIT(2) /* Host Interrupt Generate */ +#define H_CSR_IS BIT(1) /* Host Interrupt Status */ +#define H_CSR_IE BIT(0) /* Host Interrupt Enable */ +/* FW_CB_RW - FW Circular Buffer Read Window register (read only) */ +#define FW_CB_RW 0x8 +/* FW_CSR_HA - FW Control Status Host Access register (read only) */ +#define FW_CSR_HA 0xC +#define FW_CSR_CBD GENMASK(31, 24) /* FW CB (Circular Buffer) Depth */ +#define FW_CSR_CBWP GENMASK(23, 16) /* FW CB Write Pointer */ +#define FW_CSR_CBRP GENMASK(15, 8) /* FW CB Read Pointer */ +#define FW_CSR_RST BIT(4) /* FW Reset */ +#define FW_CSR_RDY BIT(3) /* FW Ready */ +#define FW_CSR_IG BIT(2) /* FW Interrupt Generate */ +#define FW_CSR_IS BIT(1) /* FW Interrupt Status */ +#define FW_CSR_IE BIT(0) /* FW Interrupt Enable */ + +#define CB_SLOT_SIZE sizeof(u32) /* Circular Buffer windows size */ + +#endif /* _ISSEI_HW_HECI_REGS_H_ */ diff --git a/drivers/misc/issei/pci_heci.c b/drivers/misc/issei/pci_heci.c new file mode 100644 index 000000000000..86c567a4bf0f --- /dev/null +++ b/drivers/misc/issei/pci_heci.c @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2023-2026 Intel Corporation */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cdev.h" +#include "hw_heci.h" +#include "hw_heci_regs.h" + +static int issei_heci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct device *dev = &pdev->dev; + const struct hw_heci_cfg *cfg; + struct issei_device *idev; + struct issei_heci_hw *hw; + char __iomem *registers; + int err; + + cfg = issei_heci_get_cfg(ent->driver_data); + if (!cfg) + return dev_err_probe(dev, -ENODEV, "no usable configuration.\n"); + + err = pcim_enable_device(pdev); + if (err) + return dev_err_probe(dev, err, "failed to enable pci device.\n"); + + pci_set_master(pdev); + + registers = pcim_iomap_region(pdev, 0, KBUILD_MODNAME); + if (IS_ERR(registers)) + return dev_err_probe(dev, PTR_ERR(registers), "failed to get pci region.\n"); + + err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); + if (err) + return dev_err_probe(dev, err, "no usable DMA configuration.\n"); + + idev = issei_register(sizeof(*hw), dev, &cfg->dma_length, issei_heci_get_ops()); + if (IS_ERR(idev)) + return dev_err_probe(dev, PTR_ERR(idev), "register failure.\n"); + + issei_heci_dev_init(idev, registers, cfg); + + pci_set_drvdata(pdev, idev); + + err = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI); + if (err < 0) { + dev_err_probe(dev, err, "pci_alloc_irq_vectors failure.\n"); + goto deregister; + } + + hw = to_heci_hw(idev); + hw->irq = pci_irq_vector(pdev, 0); + + err = request_threaded_irq(hw->irq, + issei_heci_irq_quick_handler, + NULL, + IRQF_SHARED, KBUILD_MODNAME, idev); + if (err) + goto release_irq; + + err = issei_start(idev); + if (err) { + dev_err_probe(dev, err, "init hw failure.\n"); + goto free_irq; + } + + return 0; + +free_irq: + idev->ops->irq_disable(idev); + free_irq(hw->irq, idev); +release_irq: + pci_free_irq_vectors(pdev); +deregister: + issei_deregister(idev); + return err; +} + +static void issei_heci_shutdown(struct pci_dev *pdev) +{ + struct issei_device *idev = pci_get_drvdata(pdev); + struct issei_heci_hw *hw = to_heci_hw(idev); + + issei_stop(idev); + + idev->ops->irq_disable(idev); + free_irq(hw->irq, idev); + pci_free_irq_vectors(pdev); +} + +static void issei_heci_remove(struct pci_dev *pdev) +{ + issei_heci_shutdown(pdev); + + issei_deregister(pci_get_drvdata(pdev)); +} + +static int issei_heci_pm_suspend(struct device *device) +{ + struct issei_device *idev = dev_get_drvdata(device); + + issei_stop(idev); + idev->ops->irq_disable(idev); + + return 0; +} + +static int issei_heci_pm_resume(struct device *device) +{ + struct issei_device *idev = dev_get_drvdata(device); + + return issei_start(idev); +} + +static const struct dev_pm_ops issei_heci_pm_ops = { + SYSTEM_SLEEP_PM_OPS(issei_heci_pm_suspend, issei_heci_pm_resume) +}; + +static const struct pci_device_id heci_pci_tbl[] = { + {PCI_VDEVICE(INTEL, 0xA85D)}, /* Lunar Lake M */ + {PCI_VDEVICE(INTEL, 0xE35D)}, /* Panther Lake H */ + {PCI_VDEVICE(INTEL, 0xE45D)}, /* Panther Lake P */ + {PCI_VDEVICE(INTEL, 0xD470)}, /* Nova Lake S */ + {PCI_VDEVICE(INTEL, 0xD358)}, /* Nova Lake H */ + {PCI_VDEVICE(INTEL, 0x4D5D)}, /* Wildcat Lake */ + {} +}; +MODULE_DEVICE_TABLE(pci, heci_pci_tbl); + +static struct pci_driver issei_heci_driver = { + .name = KBUILD_MODNAME, + .id_table = heci_pci_tbl, + .probe = issei_heci_probe, + .remove = issei_heci_remove, + .shutdown = issei_heci_shutdown, + .driver = { + .pm = &issei_heci_pm_ops, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; +module_pci_driver(issei_heci_driver); + +MODULE_DESCRIPTION("Intel(R) Silicon Security Engine Interface - HECI"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("INTEL_SSEI"); From 077b4d1aa01a1b34c5b768b8c7a77c74b35b190b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Tue, 26 May 2026 10:52:07 +0200 Subject: [PATCH 089/135] misc: pch_phub: Complete enum usage for device identification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recently an enum was introduced to identify the different hardware variants instead of magic constants. The respective commit however missed to adapt one code location that still checks the old values. As the values shifted by one this is a relevant fix. Fixes: 7b1d4ad96ea4 ("misc: pch_phub: Introduce an enum for device indentification") Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/8a97d9d5fb0a4abf7032324643e3e2337b1347bd.1779785111.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/pch_phub.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/misc/pch_phub.c b/drivers/misc/pch_phub.c index 19c4fa017f24..0097611b97af 100644 --- a/drivers/misc/pch_phub.c +++ b/drivers/misc/pch_phub.c @@ -83,6 +83,14 @@ #define PCH_PHUB_OROM_SIZE 15360 +enum pch_phub_type { + PCH_EG20T, + PCH_ML7213, + PCH_ML7223M, + PCH_ML7223N, + PCH_ML7831, +}; + /** * struct pch_phub_reg - PHUB register structure * @phub_id_reg: PHUB_ID register val @@ -125,7 +133,7 @@ struct pch_phub_reg { void __iomem *pch_phub_extrom_base_address; u32 pch_mac_start_address; u32 pch_opt_rom_start_address; - int ioh_type; + enum pch_phub_type ioh_type; struct pci_dev *pdev; }; @@ -344,7 +352,7 @@ static int pch_phub_write_gbe_mac_addr(struct pch_phub_reg *chip, u8 *data) int retval; int i; - if ((chip->ioh_type == 1) || (chip->ioh_type == 5)) /* EG20T or ML7831*/ + if (chip->ioh_type == PCH_EG20T || chip->ioh_type == PCH_ML7831) retval = pch_phub_gbe_serial_rom_conf(chip); else /* ML7223 */ retval = pch_phub_gbe_serial_rom_conf_mp(chip); @@ -537,14 +545,6 @@ static const struct bin_attribute pch_bin_attr = { .write = pch_phub_bin_write, }; -enum { - PCH_EG20T, - PCH_ML7213, - PCH_ML7223M, - PCH_ML7223N, - PCH_ML7831, -}; - static int pch_phub_probe(struct pci_dev *pdev, const struct pci_device_id *id) { From 5fd370856b840ed8164154bf832ed0d4dff87c8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Tue, 26 May 2026 10:52:08 +0200 Subject: [PATCH 090/135] misc: pch_phub: Drop unused members from struct pch_phub_reg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit d14b649fd99f ("misc: pch_phub: Drop two unused functions") all the register values in struct pch_phub_reg are unused. Drop them. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/459d402dad63a6cc0e230b6f2305556bb915579c.1779785111.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/pch_phub.c | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/drivers/misc/pch_phub.c b/drivers/misc/pch_phub.c index 0097611b97af..5adf7f0c84ab 100644 --- a/drivers/misc/pch_phub.c +++ b/drivers/misc/pch_phub.c @@ -93,20 +93,6 @@ enum pch_phub_type { /** * struct pch_phub_reg - PHUB register structure - * @phub_id_reg: PHUB_ID register val - * @q_pri_val_reg: QUEUE_PRI_VAL register val - * @rc_q_maxsize_reg: RC_QUEUE_MAXSIZE register val - * @bri_q_maxsize_reg: BRI_QUEUE_MAXSIZE register val - * @comp_resp_timeout_reg: COMP_RESP_TIMEOUT register val - * @bus_slave_control_reg: BUS_SLAVE_CONTROL_REG register val - * @deadlock_avoid_type_reg: DEADLOCK_AVOID_TYPE register val - * @intpin_reg_wpermit_reg0: INTPIN_REG_WPERMIT register 0 val - * @intpin_reg_wpermit_reg1: INTPIN_REG_WPERMIT register 1 val - * @intpin_reg_wpermit_reg2: INTPIN_REG_WPERMIT register 2 val - * @intpin_reg_wpermit_reg3: INTPIN_REG_WPERMIT register 3 val - * @int_reduce_control_reg: INT_REDUCE_CONTROL registers val - * @clkcfg_reg: CLK CFG register val - * @funcsel_reg: Function select register value * @pch_phub_base_address: Register base address * @pch_phub_extrom_base_address: external rom base address * @pch_mac_start_address: MAC address area start address @@ -115,20 +101,6 @@ enum pch_phub_type { * @pdev: pointer to pci device struct */ struct pch_phub_reg { - u32 phub_id_reg; - u32 q_pri_val_reg; - u32 rc_q_maxsize_reg; - u32 bri_q_maxsize_reg; - u32 comp_resp_timeout_reg; - u32 bus_slave_control_reg; - u32 deadlock_avoid_type_reg; - u32 intpin_reg_wpermit_reg0; - u32 intpin_reg_wpermit_reg1; - u32 intpin_reg_wpermit_reg2; - u32 intpin_reg_wpermit_reg3; - u32 int_reduce_control_reg[MAX_NUM_INT_REDUCE_CONTROL_REG]; - u32 clkcfg_reg; - u32 funcsel_reg; void __iomem *pch_phub_base_address; void __iomem *pch_phub_extrom_base_address; u32 pch_mac_start_address; From aefcde780b39c11d4ac9e191299b281af99dab3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Tue, 26 May 2026 10:52:09 +0200 Subject: [PATCH 091/135] misc: pch_phub: Make MAC address configuration more robust MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The comment in pch_phub_write_gbe_mac_addr() suggests that only EG20T, ML7831 and ML7223 are handled. Replace the code construct using an if with a switch that has the same semantics but issues a warning if a new device type is added to the driver without adapting this function. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/796b3667ea49d9156ecc03d1ce9668972316d90b.1779785111.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/pch_phub.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/misc/pch_phub.c b/drivers/misc/pch_phub.c index 5adf7f0c84ab..15785597da40 100644 --- a/drivers/misc/pch_phub.c +++ b/drivers/misc/pch_phub.c @@ -324,10 +324,19 @@ static int pch_phub_write_gbe_mac_addr(struct pch_phub_reg *chip, u8 *data) int retval; int i; - if (chip->ioh_type == PCH_EG20T || chip->ioh_type == PCH_ML7831) + switch (chip->ioh_type) { + case PCH_EG20T: + case PCH_ML7831: retval = pch_phub_gbe_serial_rom_conf(chip); - else /* ML7223 */ + break; + + case PCH_ML7213: + case PCH_ML7223M: + case PCH_ML7223N: retval = pch_phub_gbe_serial_rom_conf_mp(chip); + break; + } + if (retval) return retval; From 61b101c6a150057b6d512421ed108aed16e822ea Mon Sep 17 00:00:00 2001 From: Gui-Dong Han Date: Wed, 3 Jun 2026 10:11:27 +0800 Subject: [PATCH 092/135] misc: bcm-vk: Use acquire/release for msgq_inited bcm_vk_sync_msgq() fills the message queue information and then sets msgq_inited. Readers call bcm_vk_drv_access_ok() before accessing the message queues and their cached queue information. atomic_set()/atomic_read() do not order those accesses. A reader can see msgq_inited set while still seeing stale queue information. Use release when publishing the initialized queues and acquire when checking the gate. Keep the clear in bcm_vk_blk_drv_access() as atomic_set(). It closes the gate and does not publish queue state to readers. Fixes: 111d746bb476 ("misc: bcm-vk: add VK messaging support") Signed-off-by: Gui-Dong Han Link: https://patch.msgid.link/20260603021127.3285057-1-hanguidong02@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/bcm-vk/bcm_vk_msg.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/misc/bcm-vk/bcm_vk_msg.c b/drivers/misc/bcm-vk/bcm_vk_msg.c index 3916ec07ecad..2c084a6b3a92 100644 --- a/drivers/misc/bcm-vk/bcm_vk_msg.c +++ b/drivers/misc/bcm-vk/bcm_vk_msg.c @@ -108,7 +108,8 @@ u32 msgq_avail_space(const struct bcm_vk_msgq __iomem *msgq, bool bcm_vk_drv_access_ok(struct bcm_vk *vk) { - return (!!atomic_read(&vk->msgq_inited)); + /* Pair with the release store after message queue initialization. */ + return !!atomic_read_acquire(&vk->msgq_inited); } void bcm_vk_set_host_alert(struct bcm_vk *vk, u32 bit_mask) @@ -501,7 +502,8 @@ int bcm_vk_sync_msgq(struct bcm_vk *vk, bool force_sync) msgq++; } } - atomic_set(&vk->msgq_inited, 1); + /* Publish message queue info before allowing driver access. */ + atomic_set_release(&vk->msgq_inited, 1); return ret; } From 6994c8b4ef95114073a51d6143185bca39d6e5d5 Mon Sep 17 00:00:00 2001 From: David Laight Date: Mon, 8 Jun 2026 10:55:02 +0100 Subject: [PATCH 093/135] drivers/misc/enclosure: Replace strcpy() + strcat() with snprintf() While the sizeof the target buffer is (should be) ENCLOSURE_NAME_SIZE and the copies should not overrrun this stops any static analysis objecting to the unbounded strcpy() and strcat() calls Signed-off-by: David Laight Link: https://patch.msgid.link/20260608095523.2606-18-david.laight.linux@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/enclosure.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/misc/enclosure.c b/drivers/misc/enclosure.c index cf6382981777..de457378c501 100644 --- a/drivers/misc/enclosure.c +++ b/drivers/misc/enclosure.c @@ -184,8 +184,8 @@ EXPORT_SYMBOL_GPL(enclosure_unregister); static void enclosure_link_name(struct enclosure_component *cdev, char *name) { - strcpy(name, "enclosure_device:"); - strcat(name, dev_name(&cdev->cdev)); + snprintf(name, ENCLOSURE_NAME_SIZE, "enclosure_device:%s", + dev_name(&cdev->cdev)); } static void enclosure_remove_links(struct enclosure_component *cdev) From 937cd823bb2c950a935dbd32313586911caae2f2 Mon Sep 17 00:00:00 2001 From: Vu Nguyen Anh Khoa Date: Sun, 21 Jun 2026 15:57:43 +0700 Subject: [PATCH 094/135] misc: nsm: do not unlock mutex before locking it nsm_dev_ioctl() jumps to the common out label when the initial copy_from_user() fails. That failure path runs before mutex_lock(&nsm->lock), but the out label unconditionally calls mutex_unlock(&nsm->lock). Return -EFAULT directly for the pre-lock copy_from_user() failure so only paths that acquired the mutex release it. Signed-off-by: Vu Nguyen Anh Khoa Reviewed-by: Alexander Graf Reviewed-by: Arnd Bergmann Link: https://patch.msgid.link/20260621085743.76329-2-khoavna.tin.2225@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/nsm.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/misc/nsm.c b/drivers/misc/nsm.c index ef7b32742340..e39ff00714f7 100644 --- a/drivers/misc/nsm.c +++ b/drivers/misc/nsm.c @@ -365,9 +365,8 @@ static long nsm_dev_ioctl(struct file *file, unsigned int cmd, return -EINVAL; /* Copy user argument struct to kernel argument struct */ - r = -EFAULT; if (copy_from_user(&raw, argp, _IOC_SIZE(cmd))) - goto out; + return -EFAULT; mutex_lock(&nsm->lock); From 4965758a48ce8d3e193b7c48a0c91ef433724906 Mon Sep 17 00:00:00 2001 From: Batu Ada Tutkun Date: Mon, 22 Jun 2026 23:16:32 +0300 Subject: [PATCH 095/135] misc: ibmasm: add parentheses around sizeof operand sizeof used without parentheses around its operand on two occasions in r_heartbeat.c. Add them to comply with the kernel coding style. Signed-off-by: Batu Ada Tutkun Link: https://patch.msgid.link/20260622201633.2577-1-batuadatutkun@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/ibmasm/r_heartbeat.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/misc/ibmasm/r_heartbeat.c b/drivers/misc/ibmasm/r_heartbeat.c index 21c9b6a6f2c3..8d3fa0ce4b6c 100644 --- a/drivers/misc/ibmasm/r_heartbeat.c +++ b/drivers/misc/ibmasm/r_heartbeat.c @@ -51,12 +51,12 @@ int ibmasm_start_reverse_heartbeat(struct service_processor *sp, struct reverse_ int times_failed = 0; int result = 1; - cmd = ibmasm_new_command(sp, sizeof rhb_dot_cmd); + cmd = ibmasm_new_command(sp, sizeof(rhb_dot_cmd)); if (!cmd) return -ENOMEM; while (times_failed < 3) { - memcpy(cmd->buffer, (void *)&rhb_dot_cmd, sizeof rhb_dot_cmd); + memcpy(cmd->buffer, (void *)&rhb_dot_cmd, sizeof(rhb_dot_cmd)); cmd->status = IBMASM_CMD_PENDING; ibmasm_exec_command(sp, cmd); ibmasm_wait_for_response(cmd, IBMASM_CMD_TIMEOUT_NORMAL); From 808e530654a5354e6df78863a5d61e4d44e67235 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Sat, 20 Jun 2026 21:42:11 -0500 Subject: [PATCH 096/135] misc: nsm: bound the device-reported response length nsm_sendrecv_msg_locked() stores the virtqueue used-ring length reported by the NSM device into msg->resp.len without bounding it to the response buffer. A malicious or buggy backend can report a length larger than the response buffer; parse_resp_raw() then copies that many bytes out of the fixed buffer to user space, disclosing adjacent kernel heap (an out-of-bounds read). The request path already floors its length in fill_req_raw(); the response path lacks the symmetric check. Clamp the stored length to the size of the response buffer. Well-behaved devices report no more than the posted buffer size, so conforming traffic is unaffected. Fixes: b9873755a6c8 ("misc: Add Nitro Secure Module driver") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Reviewed-by: Alexander Graf Link: https://patch.msgid.link/20260620-b4-disp-a54b7dd6-v1-1-79d1f236a854@proton.me Signed-off-by: Greg Kroah-Hartman --- drivers/misc/nsm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/nsm.c b/drivers/misc/nsm.c index e39ff00714f7..bb254ee65b84 100644 --- a/drivers/misc/nsm.c +++ b/drivers/misc/nsm.c @@ -243,7 +243,7 @@ static int nsm_sendrecv_msg_locked(struct nsm *nsm) goto cleanup; } - msg->resp.len = len; + msg->resp.len = min_t(unsigned int, len, sizeof(msg->resp.data)); rc = 0; From f66c40cd90954f52809b3eabd386137f56bb6215 Mon Sep 17 00:00:00 2001 From: Yousef Alhouseen Date: Mon, 29 Jun 2026 18:06:05 +0200 Subject: [PATCH 097/135] misc: bcm-vk: validate write size before allocation bcm_vk_write() uses the user-supplied write count to size a flexible-array work entry and then copies count bytes into that array. The allocation expression is evaluated before any overflow check, so a very large count can wrap the allocation smaller than the subsequent copy. Reject empty writes, check the allocation arithmetic before kzalloc(), and initialize the __counted_by field before copying into to_v_msg[]. Signed-off-by: Yousef Alhouseen Link: https://patch.msgid.link/20260629160605.29412-1-alhouseenyousef@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/bcm-vk/bcm_vk_msg.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/drivers/misc/bcm-vk/bcm_vk_msg.c b/drivers/misc/bcm-vk/bcm_vk_msg.c index 2c084a6b3a92..17114092a284 100644 --- a/drivers/misc/bcm-vk/bcm_vk_msg.c +++ b/drivers/misc/bcm-vk/bcm_vk_msg.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -1090,6 +1091,7 @@ ssize_t bcm_vk_write(struct file *p_file, u32 q_num; u32 msg_size; u32 msgq_size; + size_t entry_size; if (!bcm_vk_drv_access_ok(vk)) return -EPERM; @@ -1097,20 +1099,26 @@ ssize_t bcm_vk_write(struct file *p_file, dev_dbg(dev, "Msg count %zu\n", count); /* first, do sanity check where count should be multiple of basic blk */ - if (count & (VK_MSGQ_BLK_SIZE - 1)) { - dev_err(dev, "Failure with size %zu not multiple of %zu\n", + if (!count || count & (VK_MSGQ_BLK_SIZE - 1)) { + dev_err(dev, "Failure with size %zu not a positive multiple of %zu\n", count, VK_MSGQ_BLK_SIZE); rc = -EINVAL; goto write_err; } + if (check_add_overflow(sizeof(*entry), count, &entry_size) || + check_add_overflow(entry_size, vk->ib_sgl_size, &entry_size)) { + rc = -EOVERFLOW; + goto write_err; + } + /* allocate the work entry + buffer for size count and inband sgl */ - entry = kzalloc(sizeof(*entry) + count + vk->ib_sgl_size, - GFP_KERNEL); + entry = kzalloc(entry_size, GFP_KERNEL); if (!entry) { rc = -ENOMEM; goto write_err; } + entry->to_v_blks = count >> VK_MSGQ_BLK_SZ_SHIFT; /* now copy msg from user space, and then formulate the work entry */ if (copy_from_user(&entry->to_v_msg[0], buf, count)) { @@ -1118,7 +1126,6 @@ ssize_t bcm_vk_write(struct file *p_file, goto write_free_ent; } - entry->to_v_blks = count >> VK_MSGQ_BLK_SZ_SHIFT; entry->ctx = ctx; /* do a check on the blk size which could not exceed queue space */ @@ -1355,4 +1362,3 @@ void bcm_vk_msg_remove(struct bcm_vk *vk) bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->to_v_msg_chan, NULL); bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->to_h_msg_chan, NULL); } - From 655faba1ccf195e22a7a83146ef6015e3271233c Mon Sep 17 00:00:00 2001 From: Gleb Markov Date: Mon, 29 Jun 2026 16:09:18 +0300 Subject: [PATCH 098/135] misc: rtsx: add missing write register handling If an error occurs at the stage of working with registers in conjunction with MCU_Block, it will not be processed. The occurrence of errors at this stage may signal an impact on writes to the device's PCI registers and is a more global problem than a driver-level security problem, but adding a handler would be a good practice. Add a missing error handling. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: c0e5f4e73a71 ("misc: rtsx: Add support for RTS5261") Signed-off-by: Gleb Markov Link: https://patch.msgid.link/20260629130920.1260-1-markov.gi@npc-ksb.ru Signed-off-by: Greg Kroah-Hartman --- drivers/misc/cardreader/rtsx_pcr.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/misc/cardreader/rtsx_pcr.c b/drivers/misc/cardreader/rtsx_pcr.c index c4d54ca2fa80..c6e602523538 100644 --- a/drivers/misc/cardreader/rtsx_pcr.c +++ b/drivers/misc/cardreader/rtsx_pcr.c @@ -1196,6 +1196,8 @@ static int rtsx_pci_init_hw(struct rtsx_pcr *pcr) /* Gating real mcu clock */ err = rtsx_pci_write_register(pcr, RTS5261_FW_CFG1, RTS5261_MCU_CLOCK_GATING, 0); + if (err < 0) + return err; err = rtsx_pci_write_register(pcr, RTS5261_REG_FPDCTL, SSC_POWER_DOWN, 0); } else { From 7bf6940d7cf41f1f0330a9bae4b7a886e18fd8e3 Mon Sep 17 00:00:00 2001 From: Yousef Alhouseen Date: Wed, 24 Jun 2026 21:09:19 +0200 Subject: [PATCH 099/135] misc: hpilo: validate device queue entries before use ilo_pkt_dequeue() trusts descriptor IDs and lengths read from the shared FIFO entry. A bad entry can select a descriptor outside the allocated queue memory or report a packet length larger than one descriptor. Reject entries whose descriptor index or packet length exceeds the queue layout before deriving the packet pointer returned to read and write paths. Signed-off-by: Yousef Alhouseen Link: https://patch.msgid.link/20260624190919.3432-1-alhouseenyousef@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/hpilo.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/misc/hpilo.c b/drivers/misc/hpilo.c index ff3f03ea577e..9b54a81b43d8 100644 --- a/drivers/misc/hpilo.c +++ b/drivers/misc/hpilo.c @@ -160,11 +160,16 @@ static int ilo_pkt_dequeue(struct ilo_hwinfo *hw, struct ccb *ccb, ret = fifo_dequeue(hw, fifobar, &entry); if (ret) { + int pkt_len; + pkt_id = get_entry_id(entry); + pkt_len = get_entry_len(entry); + if (pkt_id >= NR_QENTRY || pkt_len > desc_mem_sz(1)) + return 0; if (id) *id = pkt_id; if (len) - *len = get_entry_len(entry); + *len = pkt_len; if (pkt) *pkt = (void *)(desc + desc_mem_sz(pkt_id)); } From fd62c1f591372f7dc4c5bc041569c2f0a4a86be1 Mon Sep 17 00:00:00 2001 From: Yousef Alhouseen Date: Wed, 24 Jun 2026 20:59:25 +0200 Subject: [PATCH 100/135] misc: ibmvmc: release send buffer on write errors ibmvmc_get_valid_hmc_buffer() marks the selected send buffer busy before ibmvmc_write() validates the backing storage or copies data from user space. Error exits after that point leave the buffer permanently busy. Keep the buffer pointer until ownership is handed to the hypervisor, and mark it free again on local write failures. Also report an RDMA send failure instead of returning a successful byte count. Signed-off-by: Yousef Alhouseen Link: https://patch.msgid.link/20260624185925.2133-1-alhouseenyousef@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/ibmvmc.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/misc/ibmvmc.c b/drivers/misc/ibmvmc.c index beb18c34f20d..1f2968d9d01b 100644 --- a/drivers/misc/ibmvmc.c +++ b/drivers/misc/ibmvmc.c @@ -1040,7 +1040,7 @@ static ssize_t ibmvmc_write(struct file *file, const char *buffer, size_t count, loff_t *ppos) { struct inode *inode; - struct ibmvmc_buffer *vmc_buffer; + struct ibmvmc_buffer *vmc_buffer = NULL; struct ibmvmc_file_session *session; struct crq_server_adapter *adapter; struct ibmvmc_hmc *hmc; @@ -1130,9 +1130,15 @@ static ssize_t ibmvmc_write(struct file *file, const char *buffer, dev_dbg(adapter->dev, "write: file = 0x%lx, count = 0x%lx\n", (unsigned long)file, (unsigned long)count); - ibmvmc_send_msg(adapter, vmc_buffer, hmc, count); + if (ibmvmc_send_msg(adapter, vmc_buffer, hmc, count)) { + ret = -EIO; + goto out; + } + vmc_buffer = NULL; ret = p - buffer; out: + if (vmc_buffer) + vmc_buffer->free = 1; spin_unlock_irqrestore(&hmc->lock, flags); return (ssize_t)(ret); } From 18189e5d84aa0b3bc89189cba13b9105634cb6fb Mon Sep 17 00:00:00 2001 From: Yousef Alhouseen Date: Wed, 24 Jun 2026 19:51:39 +0200 Subject: [PATCH 101/135] misc: ibmvmc: reject oversized inbound messages ibmvmc_recv_msg() trusts the message length from the CRQ. It passes that length directly to h_copy_rdma(). The destination buffer is only max_mtu bytes. A larger length can overrun it before userspace reads the message. Validate the CRQ length before issuing the RDMA copy. Signed-off-by: Yousef Alhouseen Link: https://patch.msgid.link/20260624175139.7981-1-alhouseenyousef@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/ibmvmc.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/misc/ibmvmc.c b/drivers/misc/ibmvmc.c index 1f2968d9d01b..28bf4c352317 100644 --- a/drivers/misc/ibmvmc.c +++ b/drivers/misc/ibmvmc.c @@ -1659,6 +1659,13 @@ static int ibmvmc_recv_msg(struct crq_server_adapter *adapter, return -1; } + if (msg_len > buffer->size) { + dev_err(adapter->dev, "Recv_msg: msg_len 0x%lx exceeds buffer size 0x%x\n", + msg_len, buffer->size); + spin_unlock_irqrestore(&hmc->lock, flags); + return -1; + } + /* RDMA the data into the partition. */ rc = h_copy_rdma(msg_len, adapter->riobn, From 82aa033fef523ab4f0165afbc1c1fb0dadb89f94 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Tue, 16 Jun 2026 23:10:28 +0800 Subject: [PATCH 102/135] misc: rp1: clear chained IRQ handlers on teardown rp1_probe() installs a chained handler for each parent MSI-X vector and stores the rp1 device pointer as handler data. rp1_unregister_interrupts() then disposes the child IRQ mappings, removes the IRQ domain, and frees the PCI IRQ vectors without first removing those chained handlers. If a teardown path runs after the handlers have been installed, a later parent IRQ can still call rp1_chained_handle_irq() with stale handler data and a removed IRQ domain. Clear the chained handlers before disposing mappings and freeing the vectors. Signed-off-by: Pengpeng Hou Reviewed-by: Andrea della Porta Link: https://patch.msgid.link/20260616151028.69890-1-pengpeng@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/misc/rp1/rp1_pci.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/misc/rp1/rp1_pci.c b/drivers/misc/rp1/rp1_pci.c index 81685e3f3296..a1f20d88be5d 100644 --- a/drivers/misc/rp1/rp1_pci.c +++ b/drivers/misc/rp1/rp1_pci.c @@ -166,6 +166,9 @@ static void rp1_unregister_interrupts(struct pci_dev *pdev) struct rp1_dev *rp1 = pci_get_drvdata(pdev); int irq, i; + for (i = 0; i < RP1_INT_END; i++) + irq_set_chained_handler_and_data(pci_irq_vector(pdev, i), NULL, NULL); + if (rp1->domain) { for (i = 0; i < RP1_INT_END; i++) { irq = irq_find_mapping(rp1->domain, i); From 0764fd10406f38bf6d86077a43301f0c35cc88f4 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Tue, 16 Jun 2026 23:08:02 +0800 Subject: [PATCH 103/135] misc: rp1: do not put borrowed OF node dev_of_node() returns the device's OF node without taking a new reference. rp1_probe() stores that borrowed pointer in rp1_node, but drops it with of_node_put() on both success and failure paths. Dropping a reference that was never acquired can underflow the node's refcount and leave later users with a stale OF node. Remove the of_node_put() calls and keep rp1_node as a borrowed pointer. Signed-off-by: Pengpeng Hou Reviewed-by: Andrea della Porta Link: https://patch.msgid.link/20260616150802.52050-1-pengpeng@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/misc/rp1/rp1_pci.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/drivers/misc/rp1/rp1_pci.c b/drivers/misc/rp1/rp1_pci.c index a1f20d88be5d..0e87633fe4f8 100644 --- a/drivers/misc/rp1/rp1_pci.c +++ b/drivers/misc/rp1/rp1_pci.c @@ -194,13 +194,13 @@ static int rp1_probe(struct pci_dev *pdev, const struct pci_device_id *id) if (!rp1_node) { dev_err(dev, "Missing of_node for device\n"); err = -EINVAL; - goto err_put_node; + goto err_out; } rp1 = devm_kzalloc(&pdev->dev, sizeof(*rp1), GFP_KERNEL); if (!rp1) { err = -ENOMEM; - goto err_put_node; + goto err_out; } rp1->pdev = pdev; @@ -209,21 +209,21 @@ static int rp1_probe(struct pci_dev *pdev, const struct pci_device_id *id) dev_err(&pdev->dev, "Not initialized - is the firmware running?\n"); err = -EINVAL; - goto err_put_node; + goto err_out; } err = pcim_enable_device(pdev); if (err < 0) { err = dev_err_probe(&pdev->dev, err, "Enabling PCI device has failed"); - goto err_put_node; + goto err_out; } rp1->bar1 = pcim_iomap(pdev, 1, 0); if (!rp1->bar1) { dev_err(&pdev->dev, "Cannot map PCI BAR\n"); err = -EIO; - goto err_put_node; + goto err_out; } pci_set_master(pdev); @@ -233,11 +233,11 @@ static int rp1_probe(struct pci_dev *pdev, const struct pci_device_id *id) if (err < 0) { err = dev_err_probe(&pdev->dev, err, "Failed to allocate MSI-X vectors\n"); - goto err_put_node; + goto err_out; } else if (err != RP1_INT_END) { dev_err(&pdev->dev, "Cannot allocate enough interrupts\n"); err = -EINVAL; - goto err_put_node; + goto err_out; } pci_set_drvdata(pdev, rp1); @@ -270,15 +270,11 @@ static int rp1_probe(struct pci_dev *pdev, const struct pci_device_id *id) goto err_unregister_interrupts; } - of_node_put(rp1_node); - return 0; err_unregister_interrupts: rp1_unregister_interrupts(pdev); -err_put_node: - of_node_put(rp1_node); - +err_out: return err; } From e3a8557e88eb26278eda60bf64f2ef33ce7de8bf Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Tue, 23 Jun 2026 09:56:43 +0800 Subject: [PATCH 104/135] misc: ad525x_dpot: use driver core groups for sysfs files ad_dpot_probe() creates per-RDAC sysfs files manually and then optionally creates the command sysfs group. This leaves probe responsible for rolling back partial sysfs state and makes remove responsible for matching every file that probe created. Move the device attributes into driver core dev_groups for the I2C and SPI drivers and use an is_visible() callback to expose only the attributes supported by the probed device. With this shape, the driver core creates the sysfs files only after probe succeeds and removes them before the remove callback frees the driver data. Fixes: 4eb174bee6f8 ("ad525x_dpot: new driver for AD525x digital potentiometers") Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260623015643.36508-1-pengpeng@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/misc/ad525x_dpot-i2c.c | 1 + drivers/misc/ad525x_dpot-spi.c | 1 + drivers/misc/ad525x_dpot.c | 177 ++++++++++++++++++++------------- drivers/misc/ad525x_dpot.h | 3 + 4 files changed, 112 insertions(+), 70 deletions(-) diff --git a/drivers/misc/ad525x_dpot-i2c.c b/drivers/misc/ad525x_dpot-i2c.c index 469478f7a1d3..896ad61bb9e1 100644 --- a/drivers/misc/ad525x_dpot-i2c.c +++ b/drivers/misc/ad525x_dpot-i2c.c @@ -105,6 +105,7 @@ MODULE_DEVICE_TABLE(i2c, ad_dpot_id); static struct i2c_driver ad_dpot_i2c_driver = { .driver = { .name = "ad_dpot", + .dev_groups = ad_dpot_groups, }, .probe = ad_dpot_i2c_probe, .remove = ad_dpot_i2c_remove, diff --git a/drivers/misc/ad525x_dpot-spi.c b/drivers/misc/ad525x_dpot-spi.c index 263055bda48b..1ebe629715a8 100644 --- a/drivers/misc/ad525x_dpot-spi.c +++ b/drivers/misc/ad525x_dpot-spi.c @@ -131,6 +131,7 @@ MODULE_DEVICE_TABLE(spi, ad_dpot_spi_id); static struct spi_driver ad_dpot_spi_driver = { .driver = { .name = "ad_dpot", + .dev_groups = ad_dpot_groups, }, .probe = ad_dpot_spi_probe, .remove = ad_dpot_spi_remove, diff --git a/drivers/misc/ad525x_dpot.c b/drivers/misc/ad525x_dpot.c index 57bead9fba1b..a4e22fd4a107 100644 --- a/drivers/misc/ad525x_dpot.c +++ b/drivers/misc/ad525x_dpot.c @@ -630,66 +630,132 @@ static struct attribute *ad525x_attributes_commands[] = { NULL }; -static const struct attribute_group ad525x_group_commands = { - .attrs = ad525x_attributes_commands, +static struct attribute *ad525x_attributes[] = { + &dev_attr_rdac0.attr, + &dev_attr_rdac1.attr, + &dev_attr_rdac2.attr, + &dev_attr_rdac3.attr, + &dev_attr_rdac4.attr, + &dev_attr_rdac5.attr, + &dev_attr_eeprom0.attr, + &dev_attr_eeprom1.attr, + &dev_attr_eeprom2.attr, + &dev_attr_eeprom3.attr, + &dev_attr_eeprom4.attr, + &dev_attr_eeprom5.attr, + &dev_attr_tolerance0.attr, + &dev_attr_tolerance1.attr, + &dev_attr_tolerance2.attr, + &dev_attr_tolerance3.attr, + &dev_attr_tolerance4.attr, + &dev_attr_tolerance5.attr, + &dev_attr_otp0.attr, + &dev_attr_otp1.attr, + &dev_attr_otp2.attr, + &dev_attr_otp3.attr, + &dev_attr_otp4.attr, + &dev_attr_otp5.attr, + &dev_attr_otp0en.attr, + &dev_attr_otp1en.attr, + &dev_attr_otp2en.attr, + &dev_attr_otp3en.attr, + &dev_attr_otp4en.attr, + &dev_attr_otp5en.attr, + &dev_attr_inc_all.attr, + &dev_attr_dec_all.attr, + &dev_attr_inc_all_6db.attr, + &dev_attr_dec_all_6db.attr, + NULL }; -static int ad_dpot_add_files(struct device *dev, - unsigned int features, unsigned int rdac) +static int ad525x_attr_index(struct attribute *attr, + const struct attribute * const *attrs) { - int err = sysfs_create_file(&dev->kobj, - dpot_attrib_wipers[rdac]); - if (features & F_CMD_EEP) - err |= sysfs_create_file(&dev->kobj, - dpot_attrib_eeprom[rdac]); - if (features & F_CMD_TOL) - err |= sysfs_create_file(&dev->kobj, - dpot_attrib_tolerance[rdac]); - if (features & F_CMD_OTP) { - err |= sysfs_create_file(&dev->kobj, - dpot_attrib_otp_en[rdac]); - err |= sysfs_create_file(&dev->kobj, - dpot_attrib_otp[rdac]); - } + int i; - if (err) - dev_err(dev, "failed to register sysfs hooks for RDAC%d\n", - rdac); + for (i = 0; attrs[i]; i++) + if (attr == attrs[i]) + return i; - return err; + return -ENOENT; } -static inline void ad_dpot_remove_files(struct device *dev, - unsigned int features, unsigned int rdac) +static bool ad525x_is_command_attr(struct attribute *attr) { - sysfs_remove_file(&dev->kobj, - dpot_attrib_wipers[rdac]); - if (features & F_CMD_EEP) - sysfs_remove_file(&dev->kobj, - dpot_attrib_eeprom[rdac]); - if (features & F_CMD_TOL) - sysfs_remove_file(&dev->kobj, - dpot_attrib_tolerance[rdac]); - if (features & F_CMD_OTP) { - sysfs_remove_file(&dev->kobj, - dpot_attrib_otp_en[rdac]); - sysfs_remove_file(&dev->kobj, - dpot_attrib_otp[rdac]); + int i; + + for (i = 0; ad525x_attributes_commands[i]; i++) { + if (attr == ad525x_attributes_commands[i]) + return true; } + + return false; } +static umode_t ad525x_is_visible(struct kobject *kobj, struct attribute *attr, + int n) +{ + struct device *dev = kobj_to_dev(kobj); + struct dpot_data *data = dev_get_drvdata(dev); + int rdac; + + if (!data) + return 0; + + rdac = ad525x_attr_index(attr, dpot_attrib_wipers); + if (rdac >= 0) + return data->wipers & BIT(rdac) ? attr->mode : 0; + + rdac = ad525x_attr_index(attr, dpot_attrib_eeprom); + if (rdac >= 0) + return (data->wipers & BIT(rdac)) && (data->feat & F_CMD_EEP) ? + attr->mode : 0; + + rdac = ad525x_attr_index(attr, dpot_attrib_tolerance); + if (rdac >= 0) + return (data->wipers & BIT(rdac)) && (data->feat & F_CMD_TOL) ? + attr->mode : 0; + + rdac = ad525x_attr_index(attr, dpot_attrib_otp); + if (rdac >= 0) + return (data->wipers & BIT(rdac)) && (data->feat & F_CMD_OTP) ? + attr->mode : 0; + + rdac = ad525x_attr_index(attr, dpot_attrib_otp_en); + if (rdac >= 0) + return (data->wipers & BIT(rdac)) && (data->feat & F_CMD_OTP) ? + attr->mode : 0; + + if (ad525x_is_command_attr(attr)) + return data->feat & F_CMD_INC ? attr->mode : 0; + + return attr->mode; +} + +static const struct attribute_group ad525x_group = { + .attrs = ad525x_attributes, + .is_visible = ad525x_is_visible, +}; + +const struct attribute_group *ad_dpot_groups[] = { + &ad525x_group, + NULL +}; +EXPORT_SYMBOL(ad_dpot_groups); + int ad_dpot_probe(struct device *dev, struct ad_dpot_bus_data *bdata, unsigned long devid, const char *name) { struct dpot_data *data; - int i, err = 0; + int i; data = kzalloc_obj(struct dpot_data); if (!data) { - err = -ENOMEM; - goto exit; + dev_err(dev, "failed to create client for %s ID 0x%lX\n", + name, devid); + return -ENOMEM; } dev_set_drvdata(dev, data); @@ -705,51 +771,22 @@ int ad_dpot_probe(struct device *dev, data->wipers = DPOT_WIPERS(devid); for (i = DPOT_RDAC0; i < MAX_RDACS; i++) - if (data->wipers & (1 << i)) { - err = ad_dpot_add_files(dev, data->feat, i); - if (err) - goto exit_remove_files; + if (data->wipers & BIT(i)) { /* power-up midscale */ if (data->feat & F_RDACS_WONLY) data->rdac_cache[i] = data->max_pos / 2; } - if (data->feat & F_CMD_INC) - err = sysfs_create_group(&dev->kobj, &ad525x_group_commands); - - if (err) { - dev_err(dev, "failed to register sysfs hooks\n"); - goto exit_free; - } - dev_info(dev, "%s %d-Position Digital Potentiometer registered\n", name, data->max_pos); return 0; - -exit_remove_files: - for (i = DPOT_RDAC0; i < MAX_RDACS; i++) - if (data->wipers & (1 << i)) - ad_dpot_remove_files(dev, data->feat, i); - -exit_free: - kfree(data); - dev_set_drvdata(dev, NULL); -exit: - dev_err(dev, "failed to create client for %s ID 0x%lX\n", - name, devid); - return err; } EXPORT_SYMBOL(ad_dpot_probe); void ad_dpot_remove(struct device *dev) { struct dpot_data *data = dev_get_drvdata(dev); - int i; - - for (i = DPOT_RDAC0; i < MAX_RDACS; i++) - if (data->wipers & (1 << i)) - ad_dpot_remove_files(dev, data->feat, i); kfree(data); } diff --git a/drivers/misc/ad525x_dpot.h b/drivers/misc/ad525x_dpot.h index 72a9d6801937..2e877c89523b 100644 --- a/drivers/misc/ad525x_dpot.h +++ b/drivers/misc/ad525x_dpot.h @@ -10,6 +10,8 @@ #include +struct attribute_group; + #define DPOT_CONF(features, wipers, max_pos, uid) \ (((features) << 18) | (((wipers) & 0xFF) << 10) | \ ((max_pos & 0xF) << 6) | (uid & 0x3F)) @@ -210,5 +212,6 @@ struct ad_dpot_bus_data { int ad_dpot_probe(struct device *dev, struct ad_dpot_bus_data *bdata, unsigned long devid, const char *name); void ad_dpot_remove(struct device *dev); +extern const struct attribute_group *ad_dpot_groups[]; #endif From f6e2ed54db95286d9512b1cff38264e2f6299814 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Tue, 23 Jun 2026 09:52:48 +0800 Subject: [PATCH 105/135] misc: lan966x_pci: depopulate children on populate failure lan966x_pci_probe() applies a device-tree overlay and then populates platform children from the overlaid node. If of_platform_default_populate() creates some children and then fails, the current error path only unloads the overlay. Depopulate the children before unloading the overlay on that failure path, matching the remove path order. Fixes: 185686beb464 ("misc: Add support for LAN966x PCI device") Reviewed-by: Herve Codina Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260623015248.22721-1-pengpeng@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/misc/lan966x_pci.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/misc/lan966x_pci.c b/drivers/misc/lan966x_pci.c index 0bb90c0943bf..b0949c653e5b 100644 --- a/drivers/misc/lan966x_pci.c +++ b/drivers/misc/lan966x_pci.c @@ -183,6 +183,7 @@ static int lan966x_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i return 0; err_unload_overlay: + of_platform_depopulate(dev); lan966x_pci_unload_overlay(data); return ret; } From 7a7a0d97d541512f7a935125274b79914223c2c1 Mon Sep 17 00:00:00 2001 From: Yousef Alhouseen Date: Mon, 29 Jun 2026 17:28:57 +0200 Subject: [PATCH 106/135] misc: xilinx_sdfec: validate LDPC code register offsets The LDPC code register helpers check the target MMIO address after adding code_id * XSDFEC_LDPC_REG_JUMP to the register base. code_id is supplied through the ioctl path, so the multiplication and addition can wrap before the bounds check. Validate the code_id against the register window size before computing the final address, then write using the checked address. Signed-off-by: Yousef Alhouseen Reviewed-by: Cvetic, Dragan Link: https://patch.msgid.link/20260629152857.13553-1-alhouseenyousef@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/xilinx_sdfec.c | 74 +++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 40 deletions(-) diff --git a/drivers/misc/xilinx_sdfec.c b/drivers/misc/xilinx_sdfec.c index fe7bea3b14bf..a19eed39c516 100644 --- a/drivers/misc/xilinx_sdfec.c +++ b/drivers/misc/xilinx_sdfec.c @@ -456,10 +456,25 @@ static int xsdfec_get_turbo(struct xsdfec_dev *xsdfec, void __user *arg) return err; } +static int xsdfec_ldpc_reg_addr(struct xsdfec_dev *xsdfec, u32 base, u32 high, + u32 offset, u32 *addr) +{ + if (high < base || offset > (high - base) / XSDFEC_LDPC_REG_JUMP) { + dev_dbg(xsdfec->dev, + "LDPC register offset %u outside space 0x%x-0x%x", + offset, base, high); + return -EINVAL; + } + + *addr = base + offset * XSDFEC_LDPC_REG_JUMP; + return 0; +} + static int xsdfec_reg0_write(struct xsdfec_dev *xsdfec, u32 n, u32 k, u32 psize, u32 offset) { u32 wdata; + u32 addr; if (n < XSDFEC_REG0_N_MIN || n > XSDFEC_REG0_N_MAX || psize == 0 || (n > XSDFEC_REG0_N_MUL_P * psize) || n <= k || ((n % psize) != 0)) { @@ -476,17 +491,11 @@ static int xsdfec_reg0_write(struct xsdfec_dev *xsdfec, u32 n, u32 k, u32 psize, k = k << XSDFEC_REG0_K_LSB; wdata = k | n; - if (XSDFEC_LDPC_CODE_REG0_ADDR_BASE + (offset * XSDFEC_LDPC_REG_JUMP) > - XSDFEC_LDPC_CODE_REG0_ADDR_HIGH) { - dev_dbg(xsdfec->dev, "Writing outside of LDPC reg0 space 0x%x", - XSDFEC_LDPC_CODE_REG0_ADDR_BASE + - (offset * XSDFEC_LDPC_REG_JUMP)); + if (xsdfec_ldpc_reg_addr(xsdfec, XSDFEC_LDPC_CODE_REG0_ADDR_BASE, + XSDFEC_LDPC_CODE_REG0_ADDR_HIGH, offset, + &addr)) return -EINVAL; - } - xsdfec_regwrite(xsdfec, - XSDFEC_LDPC_CODE_REG0_ADDR_BASE + - (offset * XSDFEC_LDPC_REG_JUMP), - wdata); + xsdfec_regwrite(xsdfec, addr, wdata); return 0; } @@ -494,6 +503,7 @@ static int xsdfec_reg1_write(struct xsdfec_dev *xsdfec, u32 psize, u32 no_packing, u32 nm, u32 offset) { u32 wdata; + u32 addr; if (psize < XSDFEC_REG1_PSIZE_MIN || psize > XSDFEC_REG1_PSIZE_MAX) { dev_dbg(xsdfec->dev, "Psize is not in range"); @@ -510,17 +520,11 @@ static int xsdfec_reg1_write(struct xsdfec_dev *xsdfec, u32 psize, nm = (nm << XSDFEC_REG1_NM_LSB) & XSDFEC_REG1_NM_MASK; wdata = nm | no_packing | psize; - if (XSDFEC_LDPC_CODE_REG1_ADDR_BASE + (offset * XSDFEC_LDPC_REG_JUMP) > - XSDFEC_LDPC_CODE_REG1_ADDR_HIGH) { - dev_dbg(xsdfec->dev, "Writing outside of LDPC reg1 space 0x%x", - XSDFEC_LDPC_CODE_REG1_ADDR_BASE + - (offset * XSDFEC_LDPC_REG_JUMP)); + if (xsdfec_ldpc_reg_addr(xsdfec, XSDFEC_LDPC_CODE_REG1_ADDR_BASE, + XSDFEC_LDPC_CODE_REG1_ADDR_HIGH, offset, + &addr)) return -EINVAL; - } - xsdfec_regwrite(xsdfec, - XSDFEC_LDPC_CODE_REG1_ADDR_BASE + - (offset * XSDFEC_LDPC_REG_JUMP), - wdata); + xsdfec_regwrite(xsdfec, addr, wdata); return 0; } @@ -529,6 +533,7 @@ static int xsdfec_reg2_write(struct xsdfec_dev *xsdfec, u32 nlayers, u32 nmqc, u32 max_schedule, u32 offset) { u32 wdata; + u32 addr; if (nlayers < XSDFEC_REG2_NLAYERS_MIN || nlayers > XSDFEC_REG2_NLAYERS_MAX) { @@ -563,17 +568,11 @@ static int xsdfec_reg2_write(struct xsdfec_dev *xsdfec, u32 nlayers, u32 nmqc, wdata = (max_schedule | no_final_parity | special_qc | norm_type | nmqc | nlayers); - if (XSDFEC_LDPC_CODE_REG2_ADDR_BASE + (offset * XSDFEC_LDPC_REG_JUMP) > - XSDFEC_LDPC_CODE_REG2_ADDR_HIGH) { - dev_dbg(xsdfec->dev, "Writing outside of LDPC reg2 space 0x%x", - XSDFEC_LDPC_CODE_REG2_ADDR_BASE + - (offset * XSDFEC_LDPC_REG_JUMP)); + if (xsdfec_ldpc_reg_addr(xsdfec, XSDFEC_LDPC_CODE_REG2_ADDR_BASE, + XSDFEC_LDPC_CODE_REG2_ADDR_HIGH, offset, + &addr)) return -EINVAL; - } - xsdfec_regwrite(xsdfec, - XSDFEC_LDPC_CODE_REG2_ADDR_BASE + - (offset * XSDFEC_LDPC_REG_JUMP), - wdata); + xsdfec_regwrite(xsdfec, addr, wdata); return 0; } @@ -581,20 +580,15 @@ static int xsdfec_reg3_write(struct xsdfec_dev *xsdfec, u8 sc_off, u8 la_off, u16 qc_off, u32 offset) { u32 wdata; + u32 addr; wdata = ((qc_off << XSDFEC_REG3_QC_OFF_LSB) | (la_off << XSDFEC_REG3_LA_OFF_LSB) | sc_off); - if (XSDFEC_LDPC_CODE_REG3_ADDR_BASE + (offset * XSDFEC_LDPC_REG_JUMP) > - XSDFEC_LDPC_CODE_REG3_ADDR_HIGH) { - dev_dbg(xsdfec->dev, "Writing outside of LDPC reg3 space 0x%x", - XSDFEC_LDPC_CODE_REG3_ADDR_BASE + - (offset * XSDFEC_LDPC_REG_JUMP)); + if (xsdfec_ldpc_reg_addr(xsdfec, XSDFEC_LDPC_CODE_REG3_ADDR_BASE, + XSDFEC_LDPC_CODE_REG3_ADDR_HIGH, offset, + &addr)) return -EINVAL; - } - xsdfec_regwrite(xsdfec, - XSDFEC_LDPC_CODE_REG3_ADDR_BASE + - (offset * XSDFEC_LDPC_REG_JUMP), - wdata); + xsdfec_regwrite(xsdfec, addr, wdata); return 0; } From d401772a9e18e837631450844f835229354528a4 Mon Sep 17 00:00:00 2001 From: Yousef Alhouseen Date: Tue, 30 Jun 2026 12:49:23 +0200 Subject: [PATCH 107/135] misc: genwqe: handle a first DMA address of zero genwqe_setup_sgl() uses zero as the initial previous DMA address. DMA address zero is valid, so a first entry at that address enters the merge path before last_s has been assigned and dereferences NULL. Only merge adjacent mappings after an SGL data entry has been created. Signed-off-by: Yousef Alhouseen Link: https://patch.msgid.link/20260630104923.53827-1-alhouseenyousef@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/genwqe/card_utils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/genwqe/card_utils.c b/drivers/misc/genwqe/card_utils.c index a2c4a9b4f871..e3b5fb337182 100644 --- a/drivers/misc/genwqe/card_utils.c +++ b/drivers/misc/genwqe/card_utils.c @@ -413,7 +413,7 @@ int genwqe_setup_sgl(struct genwqe_dev *cd, struct genwqe_sgl *sgl, size -= size_to_map; map_offs = 0; - if (prev_daddr == daddr) { + if (last_s && prev_daddr == daddr) { u32 prev_len = be32_to_cpu(last_s->len); /* pr_info("daddr combining: " From a361bd5bcad55b943c5974413b25854fb87fc3f6 Mon Sep 17 00:00:00 2001 From: Yousef Alhouseen Date: Tue, 30 Jun 2026 12:49:41 +0200 Subject: [PATCH 108/135] misc: sgi-gru: fill execution status in exception details gru_retry_exception() tests cbrexecstatus after asking gru_get_cb_exception_detail() to fill the detail structure. The helper leaves that field uninitialized, so retry decisions depend on stale stack data. Populate the address, CBR state, and execution status alongside the other exception fields, matching the user exception-detail path. Signed-off-by: Yousef Alhouseen Link: https://patch.msgid.link/20260630104941.53862-1-alhouseenyousef@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/sgi-gru/grukservices.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/misc/sgi-gru/grukservices.c b/drivers/misc/sgi-gru/grukservices.c index 205945ce9e86..c16012ee976f 100644 --- a/drivers/misc/sgi-gru/grukservices.c +++ b/drivers/misc/sgi-gru/grukservices.c @@ -411,11 +411,14 @@ int gru_get_cb_exception_detail(void *cb, cbe = get_cbe(GRUBASE(cb), cbrnum); gru_flush_cache(cbe); /* CBE not coherent */ sync_core(); + excdet->cb = (unsigned long)cb; excdet->opc = cbe->opccpy; excdet->exopc = cbe->exopccpy; excdet->ecause = cbe->ecause; excdet->exceptdet0 = cbe->idef1upd; excdet->exceptdet1 = cbe->idef3upd; + excdet->cbrstate = cbe->cbrstate; + excdet->cbrexecstatus = cbe->cbrexecstatus; gru_flush_cache(cbe); return 0; } @@ -1154,4 +1157,3 @@ void gru_kservices_exit(void) if (gru_free_kernel_contexts()) BUG(); } - From 1bb5c324b872c2e71fa1b12a5f59615b994501da Mon Sep 17 00:00:00 2001 From: Griffin Kroah-Hartman Date: Thu, 9 Jul 2026 15:20:52 +0200 Subject: [PATCH 109/135] misc: amd-sbi: Add null check for devm_kasprintf() Add two checks for devm_kasprintf() errors in create_misc_rmi_device(), returning -ENOMEM if the function failed. Assisted-by: gkh_clanker_t1000 CC: Arnd Bergmann CC: Greg Kroah-Hartman CC: Naveen Krishna Chatradhi CC: Akshay Gupta Signed-off-by: Griffin Kroah-Hartman Link: https://patch.msgid.link/20260709132052.211683-1-griffin@kroah.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/amd-sbi/rmi-core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/misc/amd-sbi/rmi-core.c b/drivers/misc/amd-sbi/rmi-core.c index d4238ebad3c6..95c9109101b5 100644 --- a/drivers/misc/amd-sbi/rmi-core.c +++ b/drivers/misc/amd-sbi/rmi-core.c @@ -581,6 +581,8 @@ int create_misc_rmi_device(struct sbrmi_data *data, GFP_KERNEL, "sbrmi-%x", data->dev_static_addr); + if (!data->sbrmi_misc_dev.name) + return -ENOMEM; data->sbrmi_misc_dev.minor = MISC_DYNAMIC_MINOR; data->sbrmi_misc_dev.fops = &sbrmi_fops; data->sbrmi_misc_dev.parent = dev; @@ -588,6 +590,8 @@ int create_misc_rmi_device(struct sbrmi_data *data, GFP_KERNEL, "sbrmi-%x", data->dev_static_addr); + if (!data->sbrmi_misc_dev.nodename) + return -ENOMEM; data->sbrmi_misc_dev.mode = 0600; return misc_register(&data->sbrmi_misc_dev); From 242b9a9e3df32b9fd6089e01ebdad5a95864ff26 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 13 Jul 2026 19:50:25 -0700 Subject: [PATCH 110/135] misc: keba: cp500: use pcim_enable_device() Switch from pci_enable_device() to pcim_enable_device() so the PCI device is automatically disabled on probe error and driver removal. Drop the now redundant manual pci_disable_device() and pci_clear_master() calls, since pcim's release path clears bus mastering and disables the device. Assisted-by: opencode:hy3-free Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260714025025.2055506-1-rosenp@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/keba/cp500.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/drivers/misc/keba/cp500.c b/drivers/misc/keba/cp500.c index 6c65fbf22e75..c2ca63e40814 100644 --- a/drivers/misc/keba/cp500.c +++ b/drivers/misc/keba/cp500.c @@ -887,7 +887,7 @@ static int cp500_probe(struct pci_dev *pci_dev, const struct pci_device_id *id) else return -ENODEV; - ret = pci_enable_device(pci_dev); + ret = pcim_enable_device(pci_dev); if (ret) return ret; pci_set_master(pci_dev); @@ -896,18 +896,15 @@ static int cp500_probe(struct pci_dev *pci_dev, const struct pci_device_id *id) startup.end = startup.start + cp500->devs->startup.size - 1; cp500->system_startup_addr = devm_ioremap_resource(&pci_dev->dev, &startup); - if (IS_ERR(cp500->system_startup_addr)) { - ret = PTR_ERR(cp500->system_startup_addr); - goto out_disable; - } + if (IS_ERR(cp500->system_startup_addr)) + return PTR_ERR(cp500->system_startup_addr); cp500->msix_num = pci_alloc_irq_vectors(pci_dev, CP500_NUM_MSIX_NO_MMI, CP500_NUM_MSIX, PCI_IRQ_MSIX); if (cp500->msix_num < CP500_NUM_MSIX_NO_MMI) { dev_err(&pci_dev->dev, "Hardware does not support enough MSI-X interrupts\n"); - ret = -ENODEV; - goto out_disable; + return -ENODEV; } cp500_vers = ioread32(cp500->system_startup_addr + CP500_VERSION_REG); @@ -937,9 +934,6 @@ out_unregister_nvmem: nvmem_unregister_notifier(&cp500->nvmem_notifier); out_free_irq: pci_free_irq_vectors(pci_dev); -out_disable: - pci_clear_master(pci_dev); - pci_disable_device(pci_dev); return ret; } @@ -962,9 +956,6 @@ static void cp500_remove(struct pci_dev *pci_dev) pci_set_drvdata(pci_dev, 0); pci_free_irq_vectors(pci_dev); - - pci_clear_master(pci_dev); - pci_disable_device(pci_dev); } static struct pci_device_id cp500_ids[] = { From 319138ffcf83de6547651924030eb17c0d7977e5 Mon Sep 17 00:00:00 2001 From: Prathima Date: Fri, 10 Jul 2026 16:46:35 +0530 Subject: [PATCH 111/135] hwmon/misc: amd-sbi: Move core sbtsi support from hwmon to misc Move SBTSI(Side-Band Temperature Sensor Interface) core functionality out of the hwmon-only path and into drivers/misc/amd-sbi so it can be reused by non-hwmon consumers. I2C probe parsing is moved from drivers/hwmon/sbtsi_temp.c into drivers/misc/amd-sbi/tsi.c under CONFIG_AMD_SBTSI. The core driver stores struct sbtsi_data on the bus device and registers an auxiliary device amd-sbtsi.temp-sensor. per target. The current hwmon temp sensor will now require the CONFIG_AMD_SBTSI configuration as a new dependency. This split prepares the driver for additional interfaces while keeping hwmon support in hwmon subsystem on top of common SBTSI core logic. Add platform dependencies to clarify this driver is intended to run on the BMC and not on the managed node. Reviewed-by: Akshay Gupta Signed-off-by: Prathima Acked-by: Guenter Roeck Link: https://patch.msgid.link/20260710111642.850022-2-Akshay.Gupta@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/hwmon/Kconfig | 2 +- drivers/hwmon/sbtsi_temp.c | 71 ++++-------------- drivers/misc/amd-sbi/Kconfig | 13 ++++ drivers/misc/amd-sbi/Makefile | 3 + drivers/misc/amd-sbi/tsi.c | 135 ++++++++++++++++++++++++++++++++++ include/linux/misc/tsi.h | 34 +++++++++ 6 files changed, 199 insertions(+), 59 deletions(-) create mode 100644 drivers/misc/amd-sbi/tsi.c create mode 100644 include/linux/misc/tsi.h diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 2bfbcc033d59..2b4342abc0a7 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1976,7 +1976,7 @@ config SENSORS_SL28CPLD config SENSORS_SBTSI tristate "Emulated SB-TSI temperature sensor" - depends on I2C + depends on AMD_SBTSI help If you say yes here you get support for emulated temperature sensors on AMD SoCs with SB-TSI interface connected to a BMC device. diff --git a/drivers/hwmon/sbtsi_temp.c b/drivers/hwmon/sbtsi_temp.c index c28f8625cd3a..28258bf49922 100644 --- a/drivers/hwmon/sbtsi_temp.c +++ b/drivers/hwmon/sbtsi_temp.c @@ -7,13 +7,12 @@ * Copyright (c) 2020, Kun Yi */ +#include #include -#include -#include #include +#include #include -#include -#include +#include /* * SB-TSI registers only support SMBus byte data access. "_INT" registers are @@ -22,39 +21,17 @@ */ #define SBTSI_REG_TEMP_INT 0x01 /* RO */ #define SBTSI_REG_STATUS 0x02 /* RO */ -#define SBTSI_REG_CONFIG 0x03 /* RO */ #define SBTSI_REG_TEMP_HIGH_INT 0x07 /* RW */ #define SBTSI_REG_TEMP_LOW_INT 0x08 /* RW */ #define SBTSI_REG_TEMP_DEC 0x10 /* RW */ #define SBTSI_REG_TEMP_HIGH_DEC 0x13 /* RW */ #define SBTSI_REG_TEMP_LOW_DEC 0x14 /* RW */ -/* - * Bit for reporting value with temperature measurement range. - * bit == 0: Use default temperature range (0C to 255.875C). - * bit == 1: Use extended temperature range (-49C to +206.875C). - */ -#define SBTSI_CONFIG_EXT_RANGE_SHIFT 2 -/* - * ReadOrder bit specifies the reading order of integer and decimal part of - * CPU temperature for atomic reads. If bit == 0, reading integer part triggers - * latching of the decimal part, so integer part should be read first. - * If bit == 1, read order should be reversed. - */ -#define SBTSI_CONFIG_READ_ORDER_SHIFT 5 - #define SBTSI_TEMP_EXT_RANGE_ADJ 49000 #define SBTSI_TEMP_MIN 0 #define SBTSI_TEMP_MAX 255875 -/* Each client has this additional data */ -struct sbtsi_data { - struct i2c_client *client; - bool ext_range_mode; - bool read_order; -}; - /* * From SB-TSI spec: CPU temperature readings and limit registers encode the * temperature in increments of 0.125 from 0 to 255.875. The "high byte" @@ -195,55 +172,33 @@ static const struct hwmon_chip_info sbtsi_chip_info = { .info = sbtsi_info, }; -static int sbtsi_probe(struct i2c_client *client) +static int sbtsi_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *id) { - struct device *dev = &client->dev; + struct sbtsi_data *data = dev_get_drvdata(adev->dev.parent); + struct device *dev = &adev->dev; struct device *hwmon_dev; - struct sbtsi_data *data; - int err; - data = devm_kzalloc(dev, sizeof(struct sbtsi_data), GFP_KERNEL); - if (!data) - return -ENOMEM; - - data->client = client; - - err = i2c_smbus_read_byte_data(data->client, SBTSI_REG_CONFIG); - if (err < 0) - return err; - data->ext_range_mode = FIELD_GET(BIT(SBTSI_CONFIG_EXT_RANGE_SHIFT), err); - data->read_order = FIELD_GET(BIT(SBTSI_CONFIG_READ_ORDER_SHIFT), err); - - hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, + hwmon_dev = devm_hwmon_device_register_with_info(dev, "sbtsi", data, &sbtsi_chip_info, NULL); return PTR_ERR_OR_ZERO(hwmon_dev); } -static const struct i2c_device_id sbtsi_id[] = { - { .name = "sbtsi" }, +static const struct auxiliary_device_id sbtsi_id[] = { + { .name = AMD_SBTSI_ADEV "." AMD_SBTSI_AUX_HWMON }, { } }; -MODULE_DEVICE_TABLE(i2c, sbtsi_id); +MODULE_DEVICE_TABLE(auxiliary, sbtsi_id); -static const struct of_device_id __maybe_unused sbtsi_of_match[] = { - { - .compatible = "amd,sbtsi", - }, - { }, -}; -MODULE_DEVICE_TABLE(of, sbtsi_of_match); - -static struct i2c_driver sbtsi_driver = { +static struct auxiliary_driver sbtsi_driver = { .driver = { .name = "sbtsi", - .of_match_table = of_match_ptr(sbtsi_of_match), }, .probe = sbtsi_probe, .id_table = sbtsi_id, }; - -module_i2c_driver(sbtsi_driver); +module_auxiliary_driver(sbtsi_driver); MODULE_AUTHOR("Kun Yi "); MODULE_DESCRIPTION("Hwmon driver for AMD SB-TSI emulated sensor"); diff --git a/drivers/misc/amd-sbi/Kconfig b/drivers/misc/amd-sbi/Kconfig index 30e7fad7356c..512251690e0e 100644 --- a/drivers/misc/amd-sbi/Kconfig +++ b/drivers/misc/amd-sbi/Kconfig @@ -20,3 +20,16 @@ config AMD_SBRMI_HWMON This provides support for RMI device hardware monitoring. If enabled, a hardware monitoring device will be created for each socket in the system. + +config AMD_SBTSI + tristate "AMD side band TSI support" + depends on I2C + depends on ARM || ARM64 || COMPILE_TEST + select AUXILIARY_BUS + help + Enables support for the AMD SB-TSI (Side Band Temperature Sensor + Interface) driver, which provides access to emulated CPU temperature + sensors on AMD SoCs via an I2C connected BMC device. + + This driver can also be built as a module. If so, the module will + be called sbtsi. diff --git a/drivers/misc/amd-sbi/Makefile b/drivers/misc/amd-sbi/Makefile index 38eaaa651fd9..28f95b9e204f 100644 --- a/drivers/misc/amd-sbi/Makefile +++ b/drivers/misc/amd-sbi/Makefile @@ -2,3 +2,6 @@ sbrmi-i2c-objs += rmi-i2c.o rmi-core.o sbrmi-i2c-$(CONFIG_AMD_SBRMI_HWMON) += rmi-hwmon.o obj-$(CONFIG_AMD_SBRMI_I2C) += sbrmi-i2c.o +# SBTSI Configuration +sbtsi-objs += tsi.o +obj-$(CONFIG_AMD_SBTSI) += sbtsi.o diff --git a/drivers/misc/amd-sbi/tsi.c b/drivers/misc/amd-sbi/tsi.c new file mode 100644 index 000000000000..67d08df28429 --- /dev/null +++ b/drivers/misc/amd-sbi/tsi.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * tsi.c - AMD SBTSI I2C core driver. Probes the SBTSI device over I2C + * and publishes an auxiliary device on the auxiliary bus. + * + * Copyright (C) 2026 Advanced Micro Devices, Inc. + */ + +#include +#include +#include +#include +#include +#include + +#define SBTSI_REG_CONFIG 0x03 /* RO */ + +/* + * Bit for reporting value with temperature measurement range. + * bit == 0: Use default temperature range (0C to 255.875C). + * bit == 1: Use extended temperature range (-49C to +206.875C). + */ +#define SBTSI_CONFIG_EXT_RANGE_SHIFT 2 + +/* + * ReadOrder bit specifies the reading order of integer and decimal part of + * CPU temperature for atomic reads. If bit == 0, reading integer part triggers + * latching of the decimal part, so integer part should be read first. + */ +#define SBTSI_CONFIG_READ_ORDER_SHIFT 5 + +static void sbtsi_adev_release(struct device *dev) +{ + kfree(to_auxiliary_dev(dev)); +} + +static void sbtsi_unregister_hwmon_adev(void *_adev) +{ + struct auxiliary_device *adev = _adev; + + auxiliary_device_delete(adev); + auxiliary_device_uninit(adev); +} + +/* + * Create and publish an auxiliary device. The hwmon driver in + * drivers/hwmon/sbtsi_temp.c binds to this device. + * + * @dev: I2C device (parent of the auxiliary device) + * @dev_addr: I2C address — used as the auxiliary device instance ID so that + * each socket gets a unique name. + */ +static int sbtsi_create_hwmon_adev(struct device *dev, u8 dev_addr) +{ + struct auxiliary_device *adev; + int ret; + + adev = kzalloc_obj(*adev); + if (!adev) + return -ENOMEM; + + adev->name = AMD_SBTSI_AUX_HWMON; + adev->id = dev_addr; + adev->dev.parent = dev; + adev->dev.release = sbtsi_adev_release; + + ret = auxiliary_device_init(adev); + if (ret) { + kfree(adev); + return ret; + } + + ret = __auxiliary_device_add(adev, AMD_SBTSI_ADEV); + if (ret) { + auxiliary_device_uninit(adev); + return ret; + } + + return devm_add_action_or_reset(dev, sbtsi_unregister_hwmon_adev, adev); +} + +static int sbtsi_i2c_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct sbtsi_data *data; + int err; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + err = i2c_smbus_read_byte_data(data->client, SBTSI_REG_CONFIG); + if (err < 0) + return err; + data->ext_range_mode = FIELD_GET(BIT(SBTSI_CONFIG_EXT_RANGE_SHIFT), err); + data->read_order = FIELD_GET(BIT(SBTSI_CONFIG_READ_ORDER_SHIFT), err); + + dev_set_drvdata(dev, data); + /* In a multi-socket system, devices that are otherwise identical do not + * share the same static address; each instance resides at a unique I2C + * client address on the same or different bus. Use the I2C client + * address as the auxiliary device instance ID to ensure each socket + * receives a distinct auxiliary device name. + */ + return sbtsi_create_hwmon_adev(dev, client->addr); +} + +static const struct i2c_device_id sbtsi_id[] = { + { .name = "sbtsi" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sbtsi_id); + +static const struct of_device_id __maybe_unused sbtsi_of_match[] = { + { + .compatible = "amd,sbtsi", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, sbtsi_of_match); + +static struct i2c_driver sbtsi_driver = { + .driver = { + .name = "sbtsi-i2c", + .of_match_table = of_match_ptr(sbtsi_of_match), + }, + .probe = sbtsi_i2c_probe, + .id_table = sbtsi_id, +}; + +module_i2c_driver(sbtsi_driver); + +MODULE_DESCRIPTION("AMD SB-TSI I2C core driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/misc/tsi.h b/include/linux/misc/tsi.h new file mode 100644 index 000000000000..befdc2d14160 --- /dev/null +++ b/include/linux/misc/tsi.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * AMD SBTSI shared data structure and auxiliary bus definitions. + * + * Copyright (C) 2026 Advanced Micro Devices, Inc. + */ + +#ifndef _LINUX_MISC_TSI_H_ +#define _LINUX_MISC_TSI_H_ + +#include +#include + +/** + * struct sbtsi_data - driver private data for an AMD SB-TSI device + * @client: underlying I2C client + * @ext_range_mode: sensor uses extended temperature range + * @read_order: if set, decimal part must be read before integer part + */ +struct sbtsi_data { + struct i2c_client *client; + bool ext_range_mode; + bool read_order; +}; + +/* + * Name of the auxiliary device published on the auxiliary bus by the core + * driver. The full device name is "amd-sbtsi.temp-sensor.". where + * is the auxiliary device instance id. + */ +#define AMD_SBTSI_ADEV "amd-sbtsi" +#define AMD_SBTSI_AUX_HWMON "temp-sensor" + +#endif /* _LINUX_MISC_TSI_H_ */ From 54a7848c24a5e786d25b209f61d3c86528bdce62 Mon Sep 17 00:00:00 2001 From: Prathima Date: Fri, 10 Jul 2026 16:46:36 +0530 Subject: [PATCH 112/135] hwmon: sbtsi_temp: Refactor temperature register access into helpers Extract the paired integer/decimal register reads and writes from the hwmon read/write callbacks into sbtsi_temp_read() and sbtsi_temp_write() helpers. This consolidates error handling and respects the ReadOrder bit for atomic temperature latching. This keeps register access independent while preserving existing hwmon functionality. Reviewed-by: Akshay Gupta Signed-off-by: Prathima Acked-by: Guenter Roeck Link: https://patch.msgid.link/20260710111642.850022-3-Akshay.Gupta@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/hwmon/sbtsi_temp.c | 84 +++++++++++++++++++++++++++----------- 1 file changed, 61 insertions(+), 23 deletions(-) diff --git a/drivers/hwmon/sbtsi_temp.c b/drivers/hwmon/sbtsi_temp.c index 28258bf49922..078f4ab25bde 100644 --- a/drivers/hwmon/sbtsi_temp.c +++ b/drivers/hwmon/sbtsi_temp.c @@ -61,40 +61,82 @@ static inline void sbtsi_mc_to_reg(s32 temp, u8 *integer, u8 *decimal) *decimal = (temp & 0x7) << 5; } +/* + * Read integer and decimal parts of an SB-TSI temperature register pair + * The read order is determined by the ReadOrder bit to ensure atomic latching. + */ +static int sbtsi_temp_read(struct sbtsi_data *data, u8 reg1, u8 reg2, + u8 *val1, u8 *val2) +{ + int ret; + + ret = i2c_smbus_read_byte_data(data->client, reg1); + if (ret < 0) + return ret; + *val1 = ret; + ret = i2c_smbus_read_byte_data(data->client, reg2); + if (ret < 0) + return ret; + *val2 = ret; + return 0; +} + +/* + * Write integer and decimal parts of an SB-TSI temperature register pair. + */ +static int sbtsi_temp_write(struct sbtsi_data *data, u8 reg_int, u8 reg_dec, + u8 val_int, u8 val_dec) +{ + int ret; + + ret = i2c_smbus_write_byte_data(data->client, reg_int, val_int); + if (!ret) + ret = i2c_smbus_write_byte_data(data->client, reg_dec, val_dec); + return ret; +} + static int sbtsi_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { struct sbtsi_data *data = dev_get_drvdata(dev); s32 temp_int, temp_dec; + int err; + u8 val_int, val_dec; switch (attr) { case hwmon_temp_input: - if (data->read_order) { - temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_DEC); - temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_INT); - } else { - temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_INT); - temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_DEC); - } + if (data->read_order) + err = sbtsi_temp_read(data, + SBTSI_REG_TEMP_DEC, SBTSI_REG_TEMP_INT, + &val_dec, &val_int); + else + err = sbtsi_temp_read(data, + SBTSI_REG_TEMP_INT, SBTSI_REG_TEMP_DEC, + &val_int, &val_dec); + if (err < 0) + return err; break; case hwmon_temp_max: - temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_HIGH_INT); - temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_HIGH_DEC); + err = sbtsi_temp_read(data, + SBTSI_REG_TEMP_HIGH_INT, SBTSI_REG_TEMP_HIGH_DEC, + &val_int, &val_dec); + if (err < 0) + return err; break; case hwmon_temp_min: - temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_LOW_INT); - temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_LOW_DEC); + err = sbtsi_temp_read(data, + SBTSI_REG_TEMP_LOW_INT, SBTSI_REG_TEMP_LOW_DEC, + &val_int, &val_dec); + + if (err < 0) + return err; break; default: return -EINVAL; } - - if (temp_int < 0) - return temp_int; - if (temp_dec < 0) - return temp_dec; - + temp_int = val_int; + temp_dec = val_dec; *val = sbtsi_reg_to_mc(temp_int, temp_dec); if (data->ext_range_mode) *val -= SBTSI_TEMP_EXT_RANGE_ADJ; @@ -106,7 +148,7 @@ static int sbtsi_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long val) { struct sbtsi_data *data = dev_get_drvdata(dev); - int reg_int, reg_dec, err; + int reg_int, reg_dec; u8 temp_int, temp_dec; switch (attr) { @@ -127,11 +169,7 @@ static int sbtsi_write(struct device *dev, enum hwmon_sensor_types type, val = clamp_val(val, SBTSI_TEMP_MIN, SBTSI_TEMP_MAX); sbtsi_mc_to_reg(val, &temp_int, &temp_dec); - err = i2c_smbus_write_byte_data(data->client, reg_int, temp_int); - if (err) - return err; - - return i2c_smbus_write_byte_data(data->client, reg_dec, temp_dec); + return sbtsi_temp_write(data, reg_int, reg_dec, temp_int, temp_dec); } static umode_t sbtsi_is_visible(const void *data, From d4f8babf8e8ffb8cd75d12d1003d2a2c17a53420 Mon Sep 17 00:00:00 2001 From: Prathima Date: Fri, 10 Jul 2026 16:46:37 +0530 Subject: [PATCH 113/135] hwmon/misc: amd-sbi: Move sbtsi register transfer to core abstraction Move the I2C read/write byte operations from the sbtsi hwmon driver into a common sbtsi_xfer() function in tsi-core.c. This decouples the hwmon sensor driver from the underlying bus transport, preparing for I3C support in a subsequent patch. This patch does not introduce any functional changes. The updates are limited to code organization/cleanup and should not affect the runtime behavior of the driver Reviewed-by: Akshay Gupta Signed-off-by: Prathima Acked-by: Guenter Roeck Link: https://patch.msgid.link/20260710111642.850022-4-Akshay.Gupta@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/hwmon/sbtsi_temp.c | 17 ++++++----------- drivers/misc/amd-sbi/Makefile | 2 +- drivers/misc/amd-sbi/tsi-core.c | 30 ++++++++++++++++++++++++++++++ include/linux/misc/tsi.h | 13 +++++++++++++ 4 files changed, 50 insertions(+), 12 deletions(-) create mode 100644 drivers/misc/amd-sbi/tsi-core.c diff --git a/drivers/hwmon/sbtsi_temp.c b/drivers/hwmon/sbtsi_temp.c index 078f4ab25bde..d7ae986d824c 100644 --- a/drivers/hwmon/sbtsi_temp.c +++ b/drivers/hwmon/sbtsi_temp.c @@ -70,15 +70,10 @@ static int sbtsi_temp_read(struct sbtsi_data *data, u8 reg1, u8 reg2, { int ret; - ret = i2c_smbus_read_byte_data(data->client, reg1); - if (ret < 0) - return ret; - *val1 = ret; - ret = i2c_smbus_read_byte_data(data->client, reg2); - if (ret < 0) - return ret; - *val2 = ret; - return 0; + ret = sbtsi_xfer(data, reg1, val1, true); + if (!ret) + ret = sbtsi_xfer(data, reg2, val2, true); + return ret; } /* @@ -89,9 +84,9 @@ static int sbtsi_temp_write(struct sbtsi_data *data, u8 reg_int, u8 reg_dec, { int ret; - ret = i2c_smbus_write_byte_data(data->client, reg_int, val_int); + ret = sbtsi_xfer(data, reg_int, &val_int, false); if (!ret) - ret = i2c_smbus_write_byte_data(data->client, reg_dec, val_dec); + ret = sbtsi_xfer(data, reg_dec, &val_dec, false); return ret; } diff --git a/drivers/misc/amd-sbi/Makefile b/drivers/misc/amd-sbi/Makefile index 28f95b9e204f..ce9321f5c601 100644 --- a/drivers/misc/amd-sbi/Makefile +++ b/drivers/misc/amd-sbi/Makefile @@ -3,5 +3,5 @@ sbrmi-i2c-objs += rmi-i2c.o rmi-core.o sbrmi-i2c-$(CONFIG_AMD_SBRMI_HWMON) += rmi-hwmon.o obj-$(CONFIG_AMD_SBRMI_I2C) += sbrmi-i2c.o # SBTSI Configuration -sbtsi-objs += tsi.o +sbtsi-objs += tsi.o tsi-core.o obj-$(CONFIG_AMD_SBTSI) += sbtsi.o diff --git a/drivers/misc/amd-sbi/tsi-core.c b/drivers/misc/amd-sbi/tsi-core.c new file mode 100644 index 000000000000..6ef1831515bb --- /dev/null +++ b/drivers/misc/amd-sbi/tsi-core.c @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * tsi-core.c - file defining SB-TSI protocols compliant + * AMD SoC device. + * + * Copyright (C) 2026 Advanced Micro Devices, Inc. + */ + +#include +#include + +/* I2C transfer function */ +static int sbtsi_i2c_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read) +{ + if (is_read) { + int ret = i2c_smbus_read_byte_data(data->client, reg); + + if (ret < 0) + return ret; + *val = ret; + return 0; + } + return i2c_smbus_write_byte_data(data->client, reg, *val); +} + +int sbtsi_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read) +{ + return sbtsi_i2c_xfer(data, reg, val, is_read); +} +EXPORT_SYMBOL_GPL(sbtsi_xfer); diff --git a/include/linux/misc/tsi.h b/include/linux/misc/tsi.h index befdc2d14160..2d2709f1ff32 100644 --- a/include/linux/misc/tsi.h +++ b/include/linux/misc/tsi.h @@ -31,4 +31,17 @@ struct sbtsi_data { #define AMD_SBTSI_ADEV "amd-sbtsi" #define AMD_SBTSI_AUX_HWMON "temp-sensor" +/** + * sbtsi_xfer - Perform a register read or write transfer on an AMD SB-TSI device. + * + * @data: Pointer to the sbtsi_data structure containing the device context + * @reg: Register address to access. + * @val: Pointer to the value to read into or write from. + * @is_read: If true, performs a read transfer and stores the result in @val. + * If false, performs a write transfer using the value in @val. + * + * Returns 0 on success, or a negative error code on failure. + */ +int sbtsi_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read); + #endif /* _LINUX_MISC_TSI_H_ */ From ba27ea7abd35d260e3b489f985ec2b4113c0fd85 Mon Sep 17 00:00:00 2001 From: Prathima Date: Fri, 10 Jul 2026 16:46:38 +0530 Subject: [PATCH 114/135] misc: amd-sbi: Consolidate Common SBTSI Probe Path Refactor shared probe procedures into sbtsi_probe_common() to ensure that I2C and I3C probes focus solely on bus-specific allocation and device configuration. The utility function reads the configuration register via sbtsi_xfer(), initializes ext_range_mode and read_order, assigns the driver data, and registers the hwmon auxiliary device. Routing register access through sbtsi_xfer() keeps the probe path bus-agnostic, so no transfer logic has to be duplicated when SB-TSI over I3C support is added in a later patch. Reviewed-by: Akshay Gupta Signed-off-by: Prathima Link: https://patch.msgid.link/20260710111642.850022-5-Akshay.Gupta@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/amd-sbi/tsi.c | 26 ++++++++++++++++++-------- include/linux/misc/tsi.h | 2 ++ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/drivers/misc/amd-sbi/tsi.c b/drivers/misc/amd-sbi/tsi.c index 67d08df28429..35b9f40741e7 100644 --- a/drivers/misc/amd-sbi/tsi.c +++ b/drivers/misc/amd-sbi/tsi.c @@ -79,31 +79,41 @@ static int sbtsi_create_hwmon_adev(struct device *dev, u8 dev_addr) return devm_add_action_or_reset(dev, sbtsi_unregister_hwmon_adev, adev); } +static int sbtsi_probe_common(struct device *dev, struct sbtsi_data *data) +{ + u8 val; + int err; + + err = sbtsi_xfer(data, SBTSI_REG_CONFIG, &val, true); + if (err) + return err; + + data->ext_range_mode = FIELD_GET(BIT(SBTSI_CONFIG_EXT_RANGE_SHIFT), val); + data->read_order = FIELD_GET(BIT(SBTSI_CONFIG_READ_ORDER_SHIFT), val); + + dev_set_drvdata(dev, data); + return sbtsi_create_hwmon_adev(dev, data->dev_addr); +} + static int sbtsi_i2c_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct sbtsi_data *data; - int err; data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; data->client = client; - err = i2c_smbus_read_byte_data(data->client, SBTSI_REG_CONFIG); - if (err < 0) - return err; - data->ext_range_mode = FIELD_GET(BIT(SBTSI_CONFIG_EXT_RANGE_SHIFT), err); - data->read_order = FIELD_GET(BIT(SBTSI_CONFIG_READ_ORDER_SHIFT), err); - dev_set_drvdata(dev, data); /* In a multi-socket system, devices that are otherwise identical do not * share the same static address; each instance resides at a unique I2C * client address on the same or different bus. Use the I2C client * address as the auxiliary device instance ID to ensure each socket * receives a distinct auxiliary device name. */ - return sbtsi_create_hwmon_adev(dev, client->addr); + data->dev_addr = client->addr; + return sbtsi_probe_common(dev, data); } static const struct i2c_device_id sbtsi_id[] = { diff --git a/include/linux/misc/tsi.h b/include/linux/misc/tsi.h index 2d2709f1ff32..6533879cc358 100644 --- a/include/linux/misc/tsi.h +++ b/include/linux/misc/tsi.h @@ -14,11 +14,13 @@ /** * struct sbtsi_data - driver private data for an AMD SB-TSI device * @client: underlying I2C client + * @dev_addr: I2C device address, used as the auxiliary device instance id * @ext_range_mode: sensor uses extended temperature range * @read_order: if set, decimal part must be read before integer part */ struct sbtsi_data { struct i2c_client *client; + u8 dev_addr; bool ext_range_mode; bool read_order; }; From f61a6fd4593bddc0f2109a1d3359fd2a88124d33 Mon Sep 17 00:00:00 2001 From: Prathima Date: Fri, 10 Jul 2026 16:46:39 +0530 Subject: [PATCH 115/135] misc: amd-sbi: Add support for SB-TSI over I3C AMD SB-TSI temperature sensors can be accessed over both I2C and I3C buses depending on the platform configuration. Extend the SB-TSI driver to support both I2C and I3C bus interfaces by selecting the appropriate transport based on the probed bus type. The driver maintains backward compatibility with existing I2C deployments while enabling support for systems using the I3C bus. Register both I2C and I3C drivers using module_i3c_i2c_driver() and update the Kconfig dependency from I2C to I3C_OR_I2C. Reviewed-by: Akshay Gupta Signed-off-by: Prathima Link: https://patch.msgid.link/20260710111642.850022-6-Akshay.Gupta@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/amd-sbi/Kconfig | 4 +- drivers/misc/amd-sbi/tsi-core.c | 55 +++++++++++++++++++++++++++- drivers/misc/amd-sbi/tsi-core.h | 26 +++++++++++++ drivers/misc/amd-sbi/tsi.c | 65 +++++++++++++++++++++++++++++++-- include/linux/misc/tsi.h | 11 +++++- 5 files changed, 152 insertions(+), 9 deletions(-) create mode 100644 drivers/misc/amd-sbi/tsi-core.h diff --git a/drivers/misc/amd-sbi/Kconfig b/drivers/misc/amd-sbi/Kconfig index 512251690e0e..1a96b71f8506 100644 --- a/drivers/misc/amd-sbi/Kconfig +++ b/drivers/misc/amd-sbi/Kconfig @@ -23,13 +23,13 @@ config AMD_SBRMI_HWMON config AMD_SBTSI tristate "AMD side band TSI support" - depends on I2C + depends on I3C_OR_I2C depends on ARM || ARM64 || COMPILE_TEST select AUXILIARY_BUS help Enables support for the AMD SB-TSI (Side Band Temperature Sensor Interface) driver, which provides access to emulated CPU temperature - sensors on AMD SoCs via an I2C connected BMC device. + sensors on AMD SoCs via an I2C/I3C connected BMC device. This driver can also be built as a module. If so, the module will be called sbtsi. diff --git a/drivers/misc/amd-sbi/tsi-core.c b/drivers/misc/amd-sbi/tsi-core.c index 6ef1831515bb..1c6f37f26d94 100644 --- a/drivers/misc/amd-sbi/tsi-core.c +++ b/drivers/misc/amd-sbi/tsi-core.c @@ -7,7 +7,12 @@ */ #include -#include +#include "tsi-core.h" + +static inline struct sbtsi_i3c_priv *to_sbtsi_i3c_priv(struct sbtsi_data *data) +{ + return container_of(data, struct sbtsi_i3c_priv, data); +} /* I2C transfer function */ static int sbtsi_i2c_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read) @@ -23,8 +28,56 @@ static int sbtsi_i2c_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read return i2c_smbus_write_byte_data(data->client, reg, *val); } +/* I3C read transfer function */ +static int sbtsi_i3c_read(struct sbtsi_data *data, u8 reg, u8 *val) +{ + struct sbtsi_i3c_priv *priv = to_sbtsi_i3c_priv(data); + struct i3c_xfer xfers[2] = { }; + int ret; + + priv->tx[0] = reg; + + /* Write the register address (DMA_TO_DEVICE). */ + xfers[0].rnw = false; + xfers[0].len = 1; + xfers[0].data.out = priv->tx; + + /* Read the data byte into a separate buffer (DMA_FROM_DEVICE). */ + xfers[1].rnw = true; + xfers[1].len = 1; + xfers[1].data.in = &priv->rx; + + ret = i3c_device_do_xfers(data->i3cdev, xfers, 2, I3C_SDR); + if (ret) + return ret; + + *val = priv->rx; + return ret; +} + +/* I3C write transfer function */ +static int sbtsi_i3c_write(struct sbtsi_data *data, u8 reg, u8 val) +{ + struct sbtsi_i3c_priv *priv = to_sbtsi_i3c_priv(data); + struct i3c_xfer xfers = { + .rnw = false, + .len = 2, + .data.out = priv->tx, + }; + + priv->tx[0] = reg; + priv->tx[1] = val; + + return i3c_device_do_xfers(data->i3cdev, &xfers, 1, I3C_SDR); +} + +/* Unified transfer function for I2C and I3C access */ int sbtsi_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read) { + if (data->is_i3c) + return is_read ? sbtsi_i3c_read(data, reg, val) + : sbtsi_i3c_write(data, reg, *val); + return sbtsi_i2c_xfer(data, reg, val, is_read); } EXPORT_SYMBOL_GPL(sbtsi_xfer); diff --git a/drivers/misc/amd-sbi/tsi-core.h b/drivers/misc/amd-sbi/tsi-core.h new file mode 100644 index 000000000000..7e8c0e7c3bcf --- /dev/null +++ b/drivers/misc/amd-sbi/tsi-core.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * AMD SBTSI core driver private definitions. + * + * Copyright (C) 2026 Advanced Micro Devices, Inc. + */ + +#ifndef _LINUX_TSI_CORE_H_ +#define _LINUX_TSI_CORE_H_ + +#include +#include + +/** + * struct sbtsi_i3c_priv - per-device state for I3C SBTSI (includes DMA-safe buffers) + * @data: public device state exposed via dev_set_drvdata() + * @tx: outgoing I3C bytes (DMA_TO_DEVICE); [0] register address, [1] value + * @rx: incoming I3C data byte (DMA_FROM_DEVICE) + */ +struct sbtsi_i3c_priv { + struct sbtsi_data data; + u8 tx[2]; + u8 rx __aligned(ARCH_DMA_MINALIGN); +}; + +#endif /* _LINUX_TSI_CORE_H_ */ diff --git a/drivers/misc/amd-sbi/tsi.c b/drivers/misc/amd-sbi/tsi.c index 35b9f40741e7..1530f440a020 100644 --- a/drivers/misc/amd-sbi/tsi.c +++ b/drivers/misc/amd-sbi/tsi.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * tsi.c - AMD SBTSI I2C core driver. Probes the SBTSI device over I2C + * tsi.c - AMD SBTSI I2C/I3C core driver. Probes the SBTSI device over I2C/I3C * and publishes an auxiliary device on the auxiliary bus. * * Copyright (C) 2026 Advanced Micro Devices, Inc. @@ -10,8 +10,8 @@ #include #include #include -#include #include +#include "tsi-core.h" #define SBTSI_REG_CONFIG 0x03 /* RO */ @@ -104,6 +104,7 @@ static int sbtsi_i2c_probe(struct i2c_client *client) if (!data) return -ENOMEM; + data->is_i3c = false; data->client = client; /* In a multi-socket system, devices that are otherwise identical do not @@ -139,7 +140,63 @@ static struct i2c_driver sbtsi_driver = { .id_table = sbtsi_id, }; -module_i2c_driver(sbtsi_driver); +static int sbtsi_i3c_probe(struct i3c_device *i3cdev) +{ + struct device *dev = i3cdev_to_dev(i3cdev); + struct i3c_device_info devinfo; + struct sbtsi_i3c_priv *i3c_priv; + struct sbtsi_data *data; -MODULE_DESCRIPTION("AMD SB-TSI I2C core driver"); + /* + * AMD OOB devices differ on basis of Instance ID, + * for SBTSI, instance ID is 0. + * As the device Id match is not on basis of Instance ID, + * add the below check to probe the SBTSI device only and + * not other OOB devices. + */ + i3c_device_get_info(i3cdev, &devinfo); + if (I3C_PID_INSTANCE_ID(devinfo.pid) != 0) + return -ENXIO; + + i3c_priv = devm_kzalloc(dev, sizeof(*i3c_priv), GFP_KERNEL); + if (!i3c_priv) + return -ENOMEM; + + data = &i3c_priv->data; + data->i3cdev = i3cdev; + data->is_i3c = true; + /* + * In a multi-socket system, otherwise identical devices do not share + * the same address; each instance is enumerated with a distinct dynamic + * (assigned) address on the I3C bus. Use that address (passed in as + * dev_addr) as the auxiliary device instance ID so that every socket + * gets a unique auxiliary device name. + */ + data->dev_addr = devinfo.dyn_addr; + + return sbtsi_probe_common(dev, data); +} + +static const struct i3c_device_id sbtsi_i3c_id[] = { + /* PID for AMD SBTSI device */ + I3C_DEVICE_EXTRA_INFO(0x112, 0x0, 0x1, NULL), /* Socket:0, Turin and Genoa */ + I3C_DEVICE_EXTRA_INFO(0x0, 0x0, 0x118, NULL), /* Socket:0, Venice */ + I3C_DEVICE_EXTRA_INFO(0x0, 0x100, 0x118, NULL), /* Socket:1, Venice */ + I3C_DEVICE_EXTRA_INFO(0x112, 0x0, 0x119, NULL), /* Socket:0, Venice */ + I3C_DEVICE_EXTRA_INFO(0x112, 0x100, 0x119, NULL), /* Socket:1, Venice */ + {} +}; +MODULE_DEVICE_TABLE(i3c, sbtsi_i3c_id); + +static struct i3c_driver sbtsi_i3c_driver = { + .driver = { + .name = "sbtsi-i3c", + }, + .probe = sbtsi_i3c_probe, + .id_table = sbtsi_i3c_id, +}; + +module_i3c_i2c_driver(sbtsi_i3c_driver, &sbtsi_driver); + +MODULE_DESCRIPTION("AMD SB-TSI I2C/I3C core driver"); MODULE_LICENSE("GPL"); diff --git a/include/linux/misc/tsi.h b/include/linux/misc/tsi.h index 6533879cc358..0bdd9d923f92 100644 --- a/include/linux/misc/tsi.h +++ b/include/linux/misc/tsi.h @@ -9,20 +9,27 @@ #define _LINUX_MISC_TSI_H_ #include +#include #include /** * struct sbtsi_data - driver private data for an AMD SB-TSI device * @client: underlying I2C client - * @dev_addr: I2C device address, used as the auxiliary device instance id + * @i3cdev: underlying I3C device (when using I3C bus) + * @dev_addr: I2C/I3C device address, used as the auxiliary device instance id * @ext_range_mode: sensor uses extended temperature range * @read_order: if set, decimal part must be read before integer part + * @is_i3c: true when the device is accessed over I3C */ struct sbtsi_data { - struct i2c_client *client; + union { + struct i2c_client *client; + struct i3c_device *i3cdev; + }; u8 dev_addr; bool ext_range_mode; bool read_order; + bool is_i3c; }; /* From 48ad55cda029dca47f4cd76b88f18fdfb1d309a8 Mon Sep 17 00:00:00 2001 From: Prathima Date: Fri, 10 Jul 2026 16:46:40 +0530 Subject: [PATCH 116/135] misc: amd-sbi: Add SBTSI ioctl register transfer interface Implement IOCTL interface for SB-TSI driver to enable userspace access to TSI register read/write operations through the AMD Advanced Platform Management Link (APML) protocol. Add an ioctl command (SBTSI_IOCTL_REG_XFER_CMD) that accepts a register address, data byte, and direction flag. The mutex is taken on the ioctl path here; the hwmon path is placed under the same lock in the next patch, which completes serialization between the hwmon and ioctl paths. Reviewed-by: Akshay Gupta Signed-off-by: Prathima Link: https://patch.msgid.link/20260710111642.850022-7-Akshay.Gupta@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/amd-sbi/tsi-core.c | 126 +++++++++++++++++++++++++++++++- drivers/misc/amd-sbi/tsi-core.h | 3 + drivers/misc/amd-sbi/tsi.c | 38 +++++++++- include/linux/misc/tsi.h | 16 ++++ include/uapi/misc/amd-apml.h | 23 ++++++ 5 files changed, 202 insertions(+), 4 deletions(-) diff --git a/drivers/misc/amd-sbi/tsi-core.c b/drivers/misc/amd-sbi/tsi-core.c index 1c6f37f26d94..5c178702c67a 100644 --- a/drivers/misc/amd-sbi/tsi-core.c +++ b/drivers/misc/amd-sbi/tsi-core.c @@ -6,7 +6,11 @@ * Copyright (C) 2026 Advanced Micro Devices, Inc. */ +#include +#include #include +#include +#include #include "tsi-core.h" static inline struct sbtsi_i3c_priv *to_sbtsi_i3c_priv(struct sbtsi_data *data) @@ -14,6 +18,17 @@ static inline struct sbtsi_i3c_priv *to_sbtsi_i3c_priv(struct sbtsi_data *data) return container_of(data, struct sbtsi_i3c_priv, data); } +void sbtsi_data_release(struct kref *kref) +{ + struct sbtsi_data *data = container_of(kref, struct sbtsi_data, kref); + + mutex_destroy(&data->lock); + if (data->is_i3c) + kfree(to_sbtsi_i3c_priv(data)); + else + kfree(data); +} + /* I2C transfer function */ static int sbtsi_i2c_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read) { @@ -77,7 +92,116 @@ int sbtsi_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read) if (data->is_i3c) return is_read ? sbtsi_i3c_read(data, reg, val) : sbtsi_i3c_write(data, reg, *val); - return sbtsi_i2c_xfer(data, reg, val, is_read); } EXPORT_SYMBOL_GPL(sbtsi_xfer); + +/* + * The mutex protects against concurrent register transfers to the device + * over the shared bus. + */ +static int sbtsi_xfer_ioctl(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read) +{ + guard(sbtsi)(data); + + if (data->detached) + return -ENODEV; + + return sbtsi_xfer(data, reg, val, is_read); +} + +static int apml_tsi_reg_xfer(struct sbtsi_data *data, + struct apml_tsi_xfer_msg __user *arg) +{ + struct apml_tsi_xfer_msg msg = { 0 }; + int ret; + + if (copy_from_user(&msg, arg, sizeof(struct apml_tsi_xfer_msg))) + return -EFAULT; + + /* + * rflag is a boolean direction flag (0 = write, 1 = read). Reject + * any other value so the upper values stay reserved for future + * extensions instead of being silently treated as a read. + */ + if (msg.pad || msg.rflag > 1) + return -EINVAL; + + ret = sbtsi_xfer_ioctl(data, msg.reg_addr, &msg.data_in_out, msg.rflag); + + if (msg.rflag && !ret) { + if (copy_to_user(arg, &msg, sizeof(struct apml_tsi_xfer_msg))) + return -EFAULT; + } + return ret; +} + +static int sbtsi_open(struct inode *inode, struct file *fp) +{ + struct sbtsi_data *data; + + data = container_of(fp->private_data, struct sbtsi_data, sbtsi_misc_dev); + scoped_guard(sbtsi, data) { + if (data->detached) + return -ENODEV; + } + + kref_get(&data->kref); + + return 0; +} + +static int sbtsi_release(struct inode *inode, struct file *fp) +{ + struct sbtsi_data *data; + + data = container_of(fp->private_data, struct sbtsi_data, sbtsi_misc_dev); + kref_put(&data->kref, sbtsi_data_release); + return 0; +} + +static long sbtsi_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + struct sbtsi_data *data; + + data = container_of(fp->private_data, struct sbtsi_data, sbtsi_misc_dev); + switch (cmd) { + case SBTSI_IOCTL_REG_XFER_CMD: + return apml_tsi_reg_xfer(data, argp); + default: + return -ENOTTY; + } +} + +static const struct file_operations sbtsi_fops = { + .owner = THIS_MODULE, + .open = sbtsi_open, + .release = sbtsi_release, + .unlocked_ioctl = sbtsi_ioctl, + .compat_ioctl = compat_ptr_ioctl, +}; + +int create_misc_tsi_device(struct sbtsi_data *data, struct device *dev) +{ + int ret; + + data->sbtsi_misc_dev.name = devm_kasprintf(dev, GFP_KERNEL, + "sbtsi-%x", data->dev_addr); + if (!data->sbtsi_misc_dev.name) + return -ENOMEM; + data->sbtsi_misc_dev.minor = MISC_DYNAMIC_MINOR; + data->sbtsi_misc_dev.fops = &sbtsi_fops; + data->sbtsi_misc_dev.parent = dev; + data->sbtsi_misc_dev.nodename = devm_kasprintf(dev, GFP_KERNEL, + "sbtsi-%x", data->dev_addr); + if (!data->sbtsi_misc_dev.nodename) + return -ENOMEM; + data->sbtsi_misc_dev.mode = 0600; + + ret = misc_register(&data->sbtsi_misc_dev); + if (ret) + return ret; + + return 0; +} diff --git a/drivers/misc/amd-sbi/tsi-core.h b/drivers/misc/amd-sbi/tsi-core.h index 7e8c0e7c3bcf..4cf55c46230e 100644 --- a/drivers/misc/amd-sbi/tsi-core.h +++ b/drivers/misc/amd-sbi/tsi-core.h @@ -23,4 +23,7 @@ struct sbtsi_i3c_priv { u8 rx __aligned(ARCH_DMA_MINALIGN); }; +int create_misc_tsi_device(struct sbtsi_data *data, struct device *dev); + +void sbtsi_data_release(struct kref *kref); #endif /* _LINUX_TSI_CORE_H_ */ diff --git a/drivers/misc/amd-sbi/tsi.c b/drivers/misc/amd-sbi/tsi.c index 1530f440a020..f06f417f451c 100644 --- a/drivers/misc/amd-sbi/tsi.c +++ b/drivers/misc/amd-sbi/tsi.c @@ -42,6 +42,23 @@ static void sbtsi_unregister_hwmon_adev(void *_adev) auxiliary_device_uninit(adev); } +static void sbtsi_misc_unregister(void *arg) +{ + struct sbtsi_data *data = arg; + + misc_deregister(&data->sbtsi_misc_dev); + + guard(sbtsi)(data); + data->detached = true; +} + +static void sbtsi_driver_unref(void *arg) +{ + struct sbtsi_data *data = arg; + + kref_put(&data->kref, sbtsi_data_release); +} + /* * Create and publish an auxiliary device. The hwmon driver in * drivers/hwmon/sbtsi_temp.c binds to this device. @@ -84,6 +101,13 @@ static int sbtsi_probe_common(struct device *dev, struct sbtsi_data *data) u8 val; int err; + mutex_init(&data->lock); + kref_init(&data->kref); + + err = devm_add_action_or_reset(dev, sbtsi_driver_unref, data); + if (err) + return err; + err = sbtsi_xfer(data, SBTSI_REG_CONFIG, &val, true); if (err) return err; @@ -92,7 +116,15 @@ static int sbtsi_probe_common(struct device *dev, struct sbtsi_data *data) data->read_order = FIELD_GET(BIT(SBTSI_CONFIG_READ_ORDER_SHIFT), val); dev_set_drvdata(dev, data); - return sbtsi_create_hwmon_adev(dev, data->dev_addr); + err = sbtsi_create_hwmon_adev(dev, data->dev_addr); + if (err < 0) + return err; + + err = create_misc_tsi_device(data, dev); + if (err) + return err; + + return devm_add_action_or_reset(dev, sbtsi_misc_unregister, data); } static int sbtsi_i2c_probe(struct i2c_client *client) @@ -100,7 +132,7 @@ static int sbtsi_i2c_probe(struct i2c_client *client) struct device *dev = &client->dev; struct sbtsi_data *data; - data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + data = kzalloc_obj(*data); if (!data) return -ENOMEM; @@ -158,7 +190,7 @@ static int sbtsi_i3c_probe(struct i3c_device *i3cdev) if (I3C_PID_INSTANCE_ID(devinfo.pid) != 0) return -ENXIO; - i3c_priv = devm_kzalloc(dev, sizeof(*i3c_priv), GFP_KERNEL); + i3c_priv = kzalloc_obj(*i3c_priv); if (!i3c_priv) return -ENOMEM; diff --git a/include/linux/misc/tsi.h b/include/linux/misc/tsi.h index 0bdd9d923f92..5273c44688f0 100644 --- a/include/linux/misc/tsi.h +++ b/include/linux/misc/tsi.h @@ -8,30 +8,46 @@ #ifndef _LINUX_MISC_TSI_H_ #define _LINUX_MISC_TSI_H_ +#include #include #include +#include +#include +#include #include /** * struct sbtsi_data - driver private data for an AMD SB-TSI device * @client: underlying I2C client * @i3cdev: underlying I3C device (when using I3C bus) + * @sbtsi_misc_dev: miscdevice exposing ioctl interface at /dev/sbtsi- + * @lock: mutex protecting concurrent access to the device + * @kref: reference count; keeps @sbtsi_data alive while misc fds are open * @dev_addr: I2C/I3C device address, used as the auxiliary device instance id + * and name the misc device node * @ext_range_mode: sensor uses extended temperature range * @read_order: if set, decimal part must be read before integer part * @is_i3c: true when the device is accessed over I3C + * @detached: set on driver unbind; open/ioctl return -ENODEV afterward */ struct sbtsi_data { union { struct i2c_client *client; struct i3c_device *i3cdev; }; + struct miscdevice sbtsi_misc_dev; + struct mutex lock; /* protects concurrent access to the device */ + struct kref kref; u8 dev_addr; bool ext_range_mode; bool read_order; bool is_i3c; + bool detached; }; +DEFINE_GUARD(sbtsi, struct sbtsi_data *, mutex_lock(&_T->lock), + mutex_unlock(&_T->lock)) + /* * Name of the auxiliary device published on the auxiliary bus by the core * driver. The full device name is "amd-sbtsi.temp-sensor.". where diff --git a/include/uapi/misc/amd-apml.h b/include/uapi/misc/amd-apml.h index 745b3338fc06..8a85f79b0938 100644 --- a/include/uapi/misc/amd-apml.h +++ b/include/uapi/misc/amd-apml.h @@ -73,6 +73,13 @@ struct apml_reg_xfer_msg { __u8 rflag; }; +struct apml_tsi_xfer_msg { + __u8 reg_addr; /* TSI register address offset */ + __u8 data_in_out; /* Register data for read/write */ + __u8 rflag; /* Register read or write */ + __u8 pad; /* Explicit padding */ +}; + /* * AMD sideband interface base IOCTL */ @@ -149,4 +156,20 @@ struct apml_reg_xfer_msg { */ #define SBRMI_IOCTL_REG_XFER_CMD _IOWR(SB_BASE_IOCTL_NR, 3, struct apml_reg_xfer_msg) +/** + * DOC: SBTSI_IOCTL_REG_XFER_CMD + * + * @Parameters + * + * @struct apml_tsi_xfer_msg + * Pointer to the &struct apml_tsi_xfer_msg that will contain the protocol + * information + * + * @Description + * IOCTL command for APML TSI messages using generic _IOWR + * The IOCTL provides userspace access to AMD sideband TSI register xfer protocol + * - TSI protocol to read/write temperature sensor registers + */ +#define SBTSI_IOCTL_REG_XFER_CMD _IOWR(SB_BASE_IOCTL_NR, 4, struct apml_tsi_xfer_msg) + #endif /*_AMD_APML_H_*/ From 47f6311f8f523ed3671611e12f94e0cbb193861b Mon Sep 17 00:00:00 2001 From: Prathima Date: Fri, 10 Jul 2026 16:46:41 +0530 Subject: [PATCH 117/135] hwmon: Add mutex protecting for sbtsi read/write through hwmon Add a mutex and take it around SBTSI read/write paths so that only one transaction runs at a time. The lock is held only for the duration of the bus transfer and associated driver bookkeeping, not across blocking work unrelated to SBTSI. This is a concurrency hardening fix. Reviewed-by: Akshay Gupta Signed-off-by: Prathima Acked-by: Guenter Roeck Link: https://patch.msgid.link/20260710111642.850022-8-Akshay.Gupta@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/hwmon/sbtsi_temp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hwmon/sbtsi_temp.c b/drivers/hwmon/sbtsi_temp.c index d7ae986d824c..11c8108d69b2 100644 --- a/drivers/hwmon/sbtsi_temp.c +++ b/drivers/hwmon/sbtsi_temp.c @@ -70,6 +70,7 @@ static int sbtsi_temp_read(struct sbtsi_data *data, u8 reg1, u8 reg2, { int ret; + guard(sbtsi)(data); ret = sbtsi_xfer(data, reg1, val1, true); if (!ret) ret = sbtsi_xfer(data, reg2, val2, true); @@ -84,6 +85,7 @@ static int sbtsi_temp_write(struct sbtsi_data *data, u8 reg_int, u8 reg_dec, { int ret; + guard(sbtsi)(data); ret = sbtsi_xfer(data, reg_int, &val_int, false); if (!ret) ret = sbtsi_xfer(data, reg_dec, &val_dec, false); From 9bdecb9173801ce558296b3f68ddc28e030a1849 Mon Sep 17 00:00:00 2001 From: Prathima Date: Fri, 10 Jul 2026 16:46:42 +0530 Subject: [PATCH 118/135] docs: misc: amd-sbi: Document SBTSI userspace interface - Document AMD sideband IOCTL description defined for SBTSI and its usage. User space C-APIs are made available by esmi_oob_library [1], which is provided by the E-SMS project [2]. Link: https://github.com/amd/esmi_oob_library [1] Link: https://www.amd.com/en/developer/e-sms.html [2] Include a user-space open example for /dev/sbtsi-* and list auxiliary bus sysfs paths. Reviewed-by: Akshay Gupta Signed-off-by: Prathima Link: https://github.com/amd/esmi_oob_library [1] Link: https://www.amd.com/en/developer/e-sms.html [2] Link: https://patch.msgid.link/20260710111642.850022-9-Akshay.Gupta@amd.com Signed-off-by: Greg Kroah-Hartman --- Documentation/misc-devices/amd-sbi.rst | 74 ++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/Documentation/misc-devices/amd-sbi.rst b/Documentation/misc-devices/amd-sbi.rst index f91ddadefe48..648d743903b7 100644 --- a/Documentation/misc-devices/amd-sbi.rst +++ b/Documentation/misc-devices/amd-sbi.rst @@ -48,6 +48,66 @@ Access restrictions: * APML Mailbox messages and Register xfer access are read-write, * CPUID and MCA_MSR access is read-only. +SBTSI device +============ + +sbtsi driver under the drivers/misc/amd-sbi creates miscdevice +/dev/sbtsi-* to let user space programs run APML TSI register transfer +commands. + +The driver supports both I2C and I3C transports for SB-TSI targets. +The transport is selected by the bus where the device is enumerated. + +Misc device: + * In 1P socket 0: /dev/sbtsi-4c + * In 2P socket 0: /dev/sbtsi-4c, socket 1: /dev/sbtsi-48 + +.. code-block:: bash + + $ ls -al /dev/sbtsi-4c + crw------- 1 root root 10, 116 Apr 2 05:22 /dev/sbtsi-4c + + +Access restrictions: + * Only root user is allowed to open the file. + * APML TSI Register transfer access is read-write. + +SBTSI hwmon interface +===================== + +The sbtsi_temp auxiliary driver binds to the auxiliary device published +by the core sbtsi driver on the auxiliary bus. The auxiliary device is +named amd-sbtsi.temp-sensor., where is the device's transfer +address: the client address for I2C, or the assigned-address for I3C. + +Note that the auxiliary bus formats in decimal, whereas the +/dev/sbtsi-* misc node formats its address in hex. The two therefore +differ for the same device: an I2C/I3C sensor at address 0x4c appears as the +misc node /dev/sbtsi-4c and the auxiliary device +amd-sbtsi.temp-sensor.76. + +It registers a hwmon device, providing a standard Linux hwmon interface +for reading CPU temperature and managing temperature limits. + +The hwmon device appears under ``/sys/class/hwmon/`` when both ``sbtsi.ko`` +and ``sbtsi_temp.ko`` are loaded. + +Verify auxiliary bus device:: + + ls /sys/bus/auxiliary/devices/ + # e.g. amd-sbtsi.temp-sensor.76 for an I2C/I3C sensor at address 0x4c + +Example usage:: + + # Read current temperature + cat /sys/class/hwmon/hwmon/temp1_input + + # Set high temperature limit to 70 °C + echo 70000 > /sys/class/hwmon/hwmon/temp1_max + + # Verify + cat /sys/class/hwmon/hwmon/temp1_max + Driver IOCTLs ============= @@ -63,6 +123,9 @@ Driver IOCTLs .. c:macro:: SBRMI_IOCTL_REG_XFER_CMD .. kernel-doc:: include/uapi/misc/amd-apml.h :doc: SBRMI_IOCTL_REG_XFER_CMD +.. c:macro:: SBTSI_IOCTL_REG_XFER_CMD +.. kernel-doc:: include/uapi/misc/amd-apml.h + :doc: SBTSI_IOCTL_REG_XFER_CMD User-space usage ================ @@ -85,6 +148,16 @@ Next thing, open the device file, as follows:: exit(1); } +To open SB-TSI device:: + + int file; + + file = open("/dev/sbtsi-4c", O_RDWR); + if (file < 0) { + /* ERROR HANDLING */ + exit(1); + } + The following IOCTLs are defined: ``#define SB_BASE_IOCTL_NR 0xF9`` @@ -92,6 +165,7 @@ The following IOCTLs are defined: ``#define SBRMI_IOCTL_CPUID_CMD _IOWR(SB_BASE_IOCTL_NR, 1, struct apml_cpuid_msg)`` ``#define SBRMI_IOCTL_MCAMSR_CMD _IOWR(SB_BASE_IOCTL_NR, 2, struct apml_mcamsr_msg)`` ``#define SBRMI_IOCTL_REG_XFER_CMD _IOWR(SB_BASE_IOCTL_NR, 3, struct apml_reg_xfer_msg)`` +``#define SBTSI_IOCTL_REG_XFER_CMD _IOWR(SB_BASE_IOCTL_NR, 4, struct apml_tsi_xfer_msg)`` User space C-APIs are made available by esmi_oob_library, hosted at From b072266078c9dfc3e59022c79575bd37796bb0bd Mon Sep 17 00:00:00 2001 From: Song Guo Date: Wed, 15 Jul 2026 12:21:44 +0000 Subject: [PATCH 119/135] misc: open-dice: do not assume dev->of_node is valid dev->of_node is not null only when the device is configured via device tree. When the matching device is configured by other means (like ACPI), the current code will cause null pointer dereference. Signed-off-by: Song Guo Link: https://patch.msgid.link/20260715122146.4069884-2-songguo@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/open-dice.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/misc/open-dice.c b/drivers/misc/open-dice.c index 45060fb4ea27..094b24dfc6ea 100644 --- a/drivers/misc/open-dice.c +++ b/drivers/misc/open-dice.c @@ -118,13 +118,18 @@ static int __init open_dice_probe(struct platform_device *pdev) { static unsigned int dev_idx; struct device *dev = &pdev->dev; - struct reserved_mem *rmem; + struct reserved_mem *rmem = NULL; struct open_dice_drvdata *drvdata; int ret; - rmem = of_reserved_mem_lookup(dev->of_node); - if (!rmem) { - dev_err(dev, "failed to lookup reserved memory\n"); + if (dev->of_node) { + rmem = of_reserved_mem_lookup(dev->of_node); + if (!rmem) { + dev_err(dev, "failed to lookup reserved memory\n"); + return -EINVAL; + } + } else { + dev_err(dev, "device not supported (no DT node)\n"); return -EINVAL; } From 9ddb0ea18fee2e60473e10989e743c918c0388ed Mon Sep 17 00:00:00 2001 From: Song Guo Date: Wed, 15 Jul 2026 12:21:45 +0000 Subject: [PATCH 120/135] misc: open-dice: save mem_base and mem_size in drvdata The reserved_mem only works on device tree systems. This commit replaced it by phys_addr_t and resource_size_t to make it possible to use open dice on non-DT platforms. Signed-off-by: Song Guo Link: https://patch.msgid.link/20260715122146.4069884-3-songguo@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/open-dice.c | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/drivers/misc/open-dice.c b/drivers/misc/open-dice.c index 094b24dfc6ea..aabece6ed3d1 100644 --- a/drivers/misc/open-dice.c +++ b/drivers/misc/open-dice.c @@ -31,7 +31,8 @@ struct open_dice_drvdata { struct mutex lock; char name[16]; - struct reserved_mem *rmem; + phys_addr_t mem_base; + resource_size_t mem_size; struct miscdevice misc; }; @@ -45,14 +46,14 @@ static int open_dice_wipe(struct open_dice_drvdata *drvdata) void *kaddr; mutex_lock(&drvdata->lock); - kaddr = devm_memremap(drvdata->misc.this_device, drvdata->rmem->base, - drvdata->rmem->size, MEMREMAP_WC); + kaddr = devm_memremap(drvdata->misc.this_device, drvdata->mem_base, + drvdata->mem_size, MEMREMAP_WC); if (IS_ERR(kaddr)) { mutex_unlock(&drvdata->lock); return PTR_ERR(kaddr); } - memset(kaddr, 0, drvdata->rmem->size); + memset(kaddr, 0, drvdata->mem_size); devm_memunmap(drvdata->misc.this_device, kaddr); mutex_unlock(&drvdata->lock); return 0; @@ -64,7 +65,7 @@ static int open_dice_wipe(struct open_dice_drvdata *drvdata) static ssize_t open_dice_read(struct file *filp, char __user *ptr, size_t len, loff_t *off) { - unsigned long val = to_open_dice_drvdata(filp)->rmem->size; + unsigned long val = to_open_dice_drvdata(filp)->mem_size; return simple_read_from_buffer(ptr, len, off, &val, sizeof(val)); } @@ -102,8 +103,8 @@ static int open_dice_mmap_prepare(struct vm_area_desc *desc) /* Create write-combine mapping so all clients observe a wipe. */ desc->page_prot = pgprot_writecombine(desc->page_prot); vma_desc_set_flags(desc, VMA_DONTCOPY_BIT, VMA_DONTDUMP_BIT); - mmap_action_simple_ioremap(desc, drvdata->rmem->base, - drvdata->rmem->size); + mmap_action_simple_ioremap(desc, drvdata->mem_base, + drvdata->mem_size); return 0; } @@ -118,27 +119,31 @@ static int __init open_dice_probe(struct platform_device *pdev) { static unsigned int dev_idx; struct device *dev = &pdev->dev; - struct reserved_mem *rmem = NULL; struct open_dice_drvdata *drvdata; + phys_addr_t mem_base; + resource_size_t mem_size; int ret; if (dev->of_node) { - rmem = of_reserved_mem_lookup(dev->of_node); + struct reserved_mem *rmem = of_reserved_mem_lookup(dev->of_node); + if (!rmem) { dev_err(dev, "failed to lookup reserved memory\n"); return -EINVAL; } + mem_base = rmem->base; + mem_size = rmem->size; } else { dev_err(dev, "device not supported (no DT node)\n"); return -EINVAL; } - if (!rmem->size || (rmem->size > ULONG_MAX)) { + if (!mem_size || (mem_size > ULONG_MAX)) { dev_err(dev, "invalid memory region size\n"); return -EINVAL; } - if (!PAGE_ALIGNED(rmem->base) || !PAGE_ALIGNED(rmem->size)) { + if (!PAGE_ALIGNED(mem_base) || !PAGE_ALIGNED(mem_size)) { dev_err(dev, "memory region must be page-aligned\n"); return -EINVAL; } @@ -148,7 +153,8 @@ static int __init open_dice_probe(struct platform_device *pdev) return -ENOMEM; *drvdata = (struct open_dice_drvdata){ - .rmem = rmem, + .mem_base = mem_base, + .mem_size = mem_size, .misc = (struct miscdevice){ .parent = dev, .name = drvdata->name, From deda667260ae495f5edc68d56e229dbfbf732243 Mon Sep 17 00:00:00 2001 From: Song Guo Date: Wed, 15 Jul 2026 12:21:46 +0000 Subject: [PATCH 121/135] misc: open-dice: add ACPI device discovery support OpenDICE can also used on x86 platforms for attestation, one of the usecase is Android's protected VM. The OpenDICE device driver only supports device tree, adding ACPI support so it can also be used on x86 environments easily. The patch is verified using crosvm, with the following ACPI table passed using --acpi-table, with --file-backed-mapping for the corresponding memory region. DefinitionBlock ( "opendice.aml", "SSDT", 2, "GOOGLE", "OpenDICE", 0x00000001 ) { Scope (\_SB) { Device (DICE) { Name (_HID, "PRP0001") Name (_DSD, Package () { ToUUID ("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package () { "compatible", Package () { "google,open-dice" } } } }) Name (_CRS, ResourceTemplate () { Memory32Fixed (ReadOnly, 0x9D1C3000, 0x00001000) }) } } } Signed-off-by: Song Guo Link: https://patch.msgid.link/20260715122146.4069884-4-songguo@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/open-dice.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/misc/open-dice.c b/drivers/misc/open-dice.c index aabece6ed3d1..303b35b03cb4 100644 --- a/drivers/misc/open-dice.c +++ b/drivers/misc/open-dice.c @@ -2,6 +2,7 @@ /* * Copyright (C) 2021 - Google LLC * Author: David Brazdil + * Author: Song Guo * * Driver for Open Profile for DICE. * @@ -19,6 +20,7 @@ * close(fd); */ +#include #include #include #include @@ -133,8 +135,17 @@ static int __init open_dice_probe(struct platform_device *pdev) } mem_base = rmem->base; mem_size = rmem->size; + } else if (is_acpi_node(dev->fwnode)) { + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (!res) { + dev_err(dev, "failed to get MMIO resource\n"); + return -EINVAL; + } + mem_base = res->start; + mem_size = resource_size(res); } else { - dev_err(dev, "device not supported (no DT node)\n"); + dev_err(dev, "device not supported (no DT or ACPI node)\n"); return -EINVAL; } @@ -218,3 +229,4 @@ module_exit(open_dice_exit); MODULE_DESCRIPTION("Driver for Open Profile for DICE."); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("David Brazdil "); +MODULE_AUTHOR("Song Guo "); From d7f0b6a15dde2e87a617095fa273f7d9c6e2d87b Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Wed, 8 Jul 2026 11:57:33 +0200 Subject: [PATCH 122/135] misc: rp1: Switch to irq_domain_create_linear() irq_domain_add_linear() is going away as being obsolete now. Switch to the preferred irq_domain_create_linear(). That differs in the first parameter: It takes more generic struct fwnode_handle instead of struct device_node. Therefore, of_fwnode_handle() is added around the parameter. Note some of the users can likely use dev->fwnode directly instead of indirect of_fwnode_handle(dev->of_node). But dev->fwnode is not guaranteed to be set for all, so this has to be investigated on case to case basis (by people who can actually test with the HW). Signed-off-by: Jiri Slaby (SUSE) Cc: Thomas Gleixner Cc: Andrea della Porta Cc: Arnd Bergmann Cc: Greg Kroah-Hartman Tested-by: Andrea della Porta Link: https://patch.msgid.link/20260708095733.385396-1-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/misc/rp1/rp1_pci.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/misc/rp1/rp1_pci.c b/drivers/misc/rp1/rp1_pci.c index 0e87633fe4f8..94ad022cd138 100644 --- a/drivers/misc/rp1/rp1_pci.c +++ b/drivers/misc/rp1/rp1_pci.c @@ -241,8 +241,8 @@ static int rp1_probe(struct pci_dev *pdev, const struct pci_device_id *id) } pci_set_drvdata(pdev, rp1); - rp1->domain = irq_domain_add_linear(rp1_node, RP1_INT_END, - &rp1_domain_ops, rp1); + rp1->domain = irq_domain_create_linear(of_fwnode_handle(rp1_node), RP1_INT_END, + &rp1_domain_ops, rp1); if (!rp1->domain) { dev_err(&pdev->dev, "Error creating IRQ domain\n"); err = -ENOMEM; From 274259391c14166fcabae74f9fc0104223ff27a1 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Thu, 11 Jun 2026 04:55:13 -0700 Subject: [PATCH 123/135] cacheinfo: don't propagate DT/ACPI error when arch supplies info (arm64) cache_setup_properties() sets use_arch_info = true when DT/ACPI provide no cache nodes and the arch can derive the topology from CPU registers (e.g. arm64 reading CLIDR_EL1), but still returns the original -ENOENT. cache_shared_cpu_map_setup() bails on that error before the new flag can take effect, so the first CPU brought online always trips a misleading warning: cacheinfo: Unable to detect cache hierarchy for CPU 0 Subsequent CPUs skip cache_setup_properties() entirely because use_arch_info is now true, which is why only CPU0 hits it. This is reproducible on arm64 with the QEMU 'virt' machine, whose default DT has no cache nodes. Clear ret after setting use_arch_info so the caller proceeds and populates the shared cpu map via the arch-supplied leaves. Fixes: ef9f643a9f8b ("cacheinfo: Add use_arch[|_cache]_info field/function") Reviewed-by: Pierre Gondois Signed-off-by: Breno Leitao Reviewed-by: Sudeep Holla Link: https://patch.msgid.link/20260611-cacheinfo-v2-1-6069ef066cf3@debian.org Signed-off-by: Greg Kroah-Hartman --- drivers/base/cacheinfo.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/base/cacheinfo.c b/drivers/base/cacheinfo.c index 70701d3bc81c..9f9c72727a05 100644 --- a/drivers/base/cacheinfo.c +++ b/drivers/base/cacheinfo.c @@ -401,9 +401,14 @@ static int cache_setup_properties(unsigned int cpu) else if (!acpi_disabled) ret = cache_setup_acpi(cpu); - // Assume there is no cache information available in DT/ACPI from now. - if (ret && use_arch_cache_info()) + /* + * No DT/ACPI cache nodes; fall back to arch-derived topology (e.g. + * arm64 CLIDR_EL1) and clear the error to avoid a spurious warning. + */ + if (ret && use_arch_cache_info()) { use_arch_info = true; + ret = 0; + } return ret; } From 3c0cf801ea2fa40daa5e7d1e6d32adca5ff75ad9 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 16 Jul 2026 09:39:23 +0800 Subject: [PATCH 124/135] ppdev: prevent overflow when setting port timeout PPSETTIME64 supplies the timeval fields as s64 values, but pp_set_timeout() narrows tv_usec to int and calculates tv_sec * HZ in a signed long. Large positive values can therefore be truncated or overflow and install an unintended timeout. Keep both fields as s64, reject a non-canonical microsecond value, and use timespec64_to_jiffies() to cap excessively large timeouts at MAX_JIFFY_OFFSET. This is a behavior change because both PPSETTIME ioctls could previously accept values with tv_usec >= USEC_PER_SEC. The validation follows the precedent set by sock_set_timeout(). Fixes: 3b9ab374a1e6 ("ppdev: convert to y2038 safe") Signed-off-by: Linmao Li Reviewed-by: Arnd Bergmann Link: https://patch.msgid.link/20260716013923.19494-1-lilinmao@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/char/ppdev.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/char/ppdev.c b/drivers/char/ppdev.c index 6da817b9849f..8803268b4cdc 100644 --- a/drivers/char/ppdev.c +++ b/drivers/char/ppdev.c @@ -340,15 +340,17 @@ static enum ieee1284_phase init_phase(int mode) return IEEE1284_PH_FWD_IDLE; } -static int pp_set_timeout(struct pardevice *pdev, long tv_sec, int tv_usec) +static int pp_set_timeout(struct pardevice *pdev, s64 tv_sec, s64 tv_usec) { + struct timespec64 ts; long to_jiffies; - if ((tv_sec < 0) || (tv_usec < 0)) + if (tv_sec < 0 || tv_usec < 0 || tv_usec >= USEC_PER_SEC) return -EINVAL; - to_jiffies = usecs_to_jiffies(tv_usec); - to_jiffies += tv_sec * HZ; + ts.tv_sec = tv_sec; + ts.tv_nsec = tv_usec * NSEC_PER_USEC; + to_jiffies = timespec64_to_jiffies(&ts); if (to_jiffies <= 0) return -EINVAL; From de1dea6fad7891ff3e7adb57cb0446e657864389 Mon Sep 17 00:00:00 2001 From: Fernando Fernandez Mancera Date: Fri, 17 Jul 2026 11:45:19 +0200 Subject: [PATCH 125/135] char: powernv-op-panel: remove unnecessary reset of position pointer The position pointer is only advanced if the return value of the write operation is positive at ksys_write(). Therefore no need to manually reset it when doing error handling. Assisted-by: coccinelle # to find it Signed-off-by: Fernando Fernandez Mancera Link: https://patch.msgid.link/20260717094519.20656-1-fmancera@suse.de Signed-off-by: Greg Kroah-Hartman --- drivers/char/powernv-op-panel.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/char/powernv-op-panel.c b/drivers/char/powernv-op-panel.c index 63175b765c90..a98acb199805 100644 --- a/drivers/char/powernv-op-panel.c +++ b/drivers/char/powernv-op-panel.c @@ -89,7 +89,6 @@ static int __op_panel_update_display(void) static ssize_t oppanel_write(struct file *filp, const char __user *userbuf, size_t len, loff_t *f_pos) { - loff_t f_pos_prev = *f_pos; ssize_t ret; int rc; @@ -105,7 +104,6 @@ static ssize_t oppanel_write(struct file *filp, const char __user *userbuf, if (rc != OPAL_SUCCESS) { pr_err_ratelimited("OPAL call failed to write to op panel display [rc=%d]\n", rc); - *f_pos = f_pos_prev; return -EIO; } } From b6b5d64cb161a28347d64dc3168a636c4abb68d5 Mon Sep 17 00:00:00 2001 From: Pei Xiao Date: Wed, 1 Jul 2026 10:01:09 +0800 Subject: [PATCH 126/135] ipack: ipoctal: fix UAF, null-ptr-deref, and use-after-free in cleanup on remove MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three issues arise when the device is removed while a tty session is still active: 1. UAF of struct ipoctal: the remove callback frees ipoctal via kfree() while tty ops may still access it. Fix by introducing kref-based lifetime management — kref is taken in install() when a tty is opened and released in cleanup() when the tty is finally destroyed; remove() uses kref_put() instead of kfree(). 2. NULL dereference in ipoctal_write_tty(): __ipoctal_remove() frees xmit_buf via tty_port_free_xmit_buf() while a userspace process may still hold the tty fd and call write(). Fix by checking for NULL xmit_buf in ipoctal_write_tty(). 3. UAF in ipoctal_cleanup(): ipack_put_carrier(ipoctal->dev) dereferences ipoctal->dev after the ipack_device has been freed by ipack_device_del(). Fix by caching ipoctal->carrier_owner during probe() and calling module_put() on the cached pointer directly in cleanup(), avoiding any access to ipoctal->dev. Also introduce a "removed" flag in struct ipoctal, set at the start of __ipoctal_remove(), and checked in every tty op that accesses hardware resources (port_activate, write_tty, set_termios, hangup, shutdown). This prevents page faults when devm_ioremap() regions are unmapped after remove() returns. Reported-by: Shuangpeng Bai Closes: https://lore.kernel.org/lkml/178144969601.60470.1257088106279546587@gmail.com/ Fixes: 05e5027efc9c ("Staging: ipack: move out of staging") Signed-off-by: Pei Xiao Link: https://patch.msgid.link/e3b0a90b07f079c5bcd5ca90d1dd3b79bb29adb5.1782870760.git.xiaopei01@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/ipack/devices/ipoctal.c | 56 ++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c index 1bbefc6de708..bf71b8952a7c 100644 --- a/drivers/ipack/devices/ipoctal.c +++ b/drivers/ipack/devices/ipoctal.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,8 @@ static const struct tty_operations ipoctal_fops; +static void ipoctal_release(struct kref *kref); + struct ipoctal_channel { struct ipoctal_stats stats; unsigned int nb_bytes; @@ -49,6 +52,9 @@ struct ipoctal { struct tty_driver *tty_drv; u8 __iomem *mem8_space; u8 __iomem *int_space; + struct kref kref; + struct module *carrier_owner; + bool removed; }; static inline struct ipoctal *chan_to_ipoctal(struct ipoctal_channel *chan, @@ -70,8 +76,14 @@ static void ipoctal_reset_channel(struct ipoctal_channel *channel) static int ipoctal_port_activate(struct tty_port *port, struct tty_struct *tty) { struct ipoctal_channel *channel; + struct ipoctal *ipoctal; channel = dev_get_drvdata(tty->dev); + ipoctal = chan_to_ipoctal(channel, tty->index); + + + if (ipoctal->removed) + return -ENODEV; /* * Enable RX. TX will be enabled when @@ -95,6 +107,7 @@ static int ipoctal_install(struct tty_driver *driver, struct tty_struct *tty) if (res) goto err_put_carrier; + kref_get(&ipoctal->kref); tty->driver_data = channel; return 0; @@ -460,8 +473,13 @@ static ssize_t ipoctal_write_tty(struct tty_struct *tty, const u8 *buf, size_t count) { struct ipoctal_channel *channel = tty->driver_data; + struct ipoctal *ipoctal = chan_to_ipoctal(channel, tty->index); size_t char_copied; + + if (ipoctal->removed || !channel->tty_port.xmit_buf) + return 0; + char_copied = ipoctal_copy_write_buffer(channel, buf, count); /* As the IP-OCTAL 485 only supports half duplex, do it manually */ @@ -501,8 +519,13 @@ static void ipoctal_set_termios(struct tty_struct *tty, unsigned char mr2 = 0; unsigned char csr = 0; struct ipoctal_channel *channel = tty->driver_data; + struct ipoctal *ipoctal = chan_to_ipoctal(channel, tty->index); speed_t baud; + + if (ipoctal->removed) + return; + cflag = tty->termios.c_cflag; /* Disable and reset everything before change the setup */ @@ -631,10 +654,16 @@ static void ipoctal_hangup(struct tty_struct *tty) { unsigned long flags; struct ipoctal_channel *channel = tty->driver_data; + struct ipoctal *ipoctal; if (channel == NULL) return; + ipoctal = chan_to_ipoctal(channel, tty->index); + + if (ipoctal->removed) + return; + spin_lock_irqsave(&channel->lock, flags); channel->nb_bytes = 0; channel->pointer_read = 0; @@ -651,10 +680,16 @@ static void ipoctal_hangup(struct tty_struct *tty) static void ipoctal_shutdown(struct tty_struct *tty) { struct ipoctal_channel *channel = tty->driver_data; + struct ipoctal *ipoctal; if (channel == NULL) return; + ipoctal = chan_to_ipoctal(channel, tty->index); + + if (ipoctal->removed) + return; + ipoctal_reset_channel(channel); tty_port_set_initialized(&channel->tty_port, false); } @@ -664,8 +699,9 @@ static void ipoctal_cleanup(struct tty_struct *tty) struct ipoctal_channel *channel = tty->driver_data; struct ipoctal *ipoctal = chan_to_ipoctal(channel, tty->index); - /* release the carrier driver */ - ipack_put_carrier(ipoctal->dev); + /* release the carrier driver via cached owner */ + module_put(ipoctal->carrier_owner); + kref_put(&ipoctal->kref, ipoctal_release); } static const struct tty_operations ipoctal_fops = { @@ -683,6 +719,13 @@ static const struct tty_operations ipoctal_fops = { .cleanup = ipoctal_cleanup, }; +static void ipoctal_release(struct kref *kref) +{ + struct ipoctal *ipoctal = container_of(kref, struct ipoctal, kref); + + kfree(ipoctal); +} + static int ipoctal_probe(struct ipack_device *dev) { int res; @@ -692,7 +735,10 @@ static int ipoctal_probe(struct ipack_device *dev) if (ipoctal == NULL) return -ENOMEM; + kref_init(&ipoctal->kref); + ipoctal->dev = dev; + ipoctal->carrier_owner = dev->bus->owner; res = ipoctal_inst_slot(ipoctal, dev->bus->bus_nr, dev->slot); if (res) goto out_uninst; @@ -701,7 +747,7 @@ static int ipoctal_probe(struct ipack_device *dev) return 0; out_uninst: - kfree(ipoctal); + kref_put(&ipoctal->kref, ipoctal_release); return res; } @@ -709,6 +755,8 @@ static void __ipoctal_remove(struct ipoctal *ipoctal) { int i; + ipoctal->removed = true; + ipoctal->dev->bus->ops->free_irq(ipoctal->dev); for (i = 0; i < NR_CHANNELS; i++) { @@ -725,7 +773,7 @@ static void __ipoctal_remove(struct ipoctal *ipoctal) tty_unregister_driver(ipoctal->tty_drv); kfree(ipoctal->tty_drv->name); tty_driver_kref_put(ipoctal->tty_drv); - kfree(ipoctal); + kref_put(&ipoctal->kref, ipoctal_release); } static void ipoctal_remove(struct ipack_device *idev) From 7d3a708af7f4e2af9e114d731bd60d30c1ec884e Mon Sep 17 00:00:00 2001 From: Pei Xiao Date: Wed, 1 Jul 2026 10:01:10 +0800 Subject: [PATCH 127/135] ipack: ipoctal: add rwsem to guard against TOCTOU in remove path The "removed" flag check in each tty op has a TOCTOU race with __ipoctal_remove(): the device could be removed between the flag check and the subsequent access to hardware resources (channel registers via iowrite8, or xmit_buf in write_tty). Close this race by introducing a read-write semaphore (remove_sem). The tty ops acquire the read lock via guard(rwsem_read) for the full duration of the operation, while __ipoctal_remove() acquires the write lock via scoped_guard(rwsem_write) when setting the removed flag. This ensures that once removed is true, no in-flight tty op can still be accessing resources that are about to be freed by the remove path. Signed-off-by: Pei Xiao Link: https://patch.msgid.link/fbce75010a0f0a3a3709a5e06fd0ffd19ca0a0ed.1782870760.git.xiaopei01@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/ipack/devices/ipoctal.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c index bf71b8952a7c..2169e4b75f98 100644 --- a/drivers/ipack/devices/ipoctal.c +++ b/drivers/ipack/devices/ipoctal.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -54,6 +55,7 @@ struct ipoctal { u8 __iomem *int_space; struct kref kref; struct module *carrier_owner; + struct rw_semaphore remove_sem; bool removed; }; @@ -81,7 +83,7 @@ static int ipoctal_port_activate(struct tty_port *port, struct tty_struct *tty) channel = dev_get_drvdata(tty->dev); ipoctal = chan_to_ipoctal(channel, tty->index); - + guard(rwsem_read)(&ipoctal->remove_sem); if (ipoctal->removed) return -ENODEV; @@ -476,7 +478,7 @@ static ssize_t ipoctal_write_tty(struct tty_struct *tty, const u8 *buf, struct ipoctal *ipoctal = chan_to_ipoctal(channel, tty->index); size_t char_copied; - + guard(rwsem_read)(&ipoctal->remove_sem); if (ipoctal->removed || !channel->tty_port.xmit_buf) return 0; @@ -522,7 +524,7 @@ static void ipoctal_set_termios(struct tty_struct *tty, struct ipoctal *ipoctal = chan_to_ipoctal(channel, tty->index); speed_t baud; - + guard(rwsem_read)(&ipoctal->remove_sem); if (ipoctal->removed) return; @@ -660,7 +662,7 @@ static void ipoctal_hangup(struct tty_struct *tty) return; ipoctal = chan_to_ipoctal(channel, tty->index); - + guard(rwsem_read)(&ipoctal->remove_sem); if (ipoctal->removed) return; @@ -686,7 +688,7 @@ static void ipoctal_shutdown(struct tty_struct *tty) return; ipoctal = chan_to_ipoctal(channel, tty->index); - + guard(rwsem_read)(&ipoctal->remove_sem); if (ipoctal->removed) return; @@ -736,6 +738,7 @@ static int ipoctal_probe(struct ipack_device *dev) return -ENOMEM; kref_init(&ipoctal->kref); + init_rwsem(&ipoctal->remove_sem); ipoctal->dev = dev; ipoctal->carrier_owner = dev->bus->owner; @@ -755,7 +758,8 @@ static void __ipoctal_remove(struct ipoctal *ipoctal) { int i; - ipoctal->removed = true; + scoped_guard(rwsem_write, &ipoctal->remove_sem) + ipoctal->removed = true; ipoctal->dev->bus->ops->free_irq(ipoctal->dev); From 18b6510f642dbcb856c3af8be974d1600f3ba2d3 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Mon, 6 Jul 2026 17:19:35 +0800 Subject: [PATCH 128/135] greybus: manifest: validate string descriptor header identify_descriptor() computes a string descriptor size from desc->string.length. Require the descriptor to contain the fixed string descriptor header before reading the variable string length. The existing descriptor-size check then reports the short descriptor. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260706091935.78020-1-pengpeng@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/greybus/manifest.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/greybus/manifest.c b/drivers/greybus/manifest.c index 9be5d95da587..3b685ace2036 100644 --- a/drivers/greybus/manifest.c +++ b/drivers/greybus/manifest.c @@ -122,6 +122,8 @@ static int identify_descriptor(struct gb_interface *intf, switch (desc_header->type) { case GREYBUS_TYPE_STRING: expected_size += sizeof(struct greybus_descriptor_string); + if (desc_size < expected_size) + break; expected_size += desc->string.length; /* String descriptors are padded to 4 byte boundaries */ From 535332e9fb99673538e6c3992659c866bdad4b23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Mon, 6 Jul 2026 18:09:32 +0200 Subject: [PATCH 129/135] greybus: Drop #include of MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The header itself also includes and additional to that doesn't make use of any symbol defined (transitively) by . Also the .c files that include that header don't need it (there is no direct include, only via ): $ git grep -l greybus\\.h | xargs grep -E "\<(acpi_device_id|amba_id|ap_device_id|apr_device_id|auxiliary_device_id|bcma_device_id|ccw_device_id|cdx_device_id|coreboot_device_id|css_device_id|dfl_device_id|dmi_(device|system)_id|eisa_device_id|fsl_mc_device_id|hda_device_id|hid_device_id|hv_vmbus_device_id|i2c_device_id|i3c_device_id|ieee1394_device_id|input_device_id|ipack_device_id|isapnp_device_id|ishtp_device_id|mcb_device_id|mdio_device_id|mei_cl_device_id|mhi_device_id|mips_cdmm_device_id|of_device_id|parisc_device_id|pci_device_id|pci_epf_device_id|pcmcia_device_id|platform_device_id|pnp_(card_)?device_id|rio_device_id|rpmsg_device_id|sdio_device_id|sdw_device_id|serio_device_id|slim_device_id|spi_device_id|spmi_device_id|ssam_device_id|ssb_device_id|tb_service_id|tee_client_device_id|typec_device_id|ulpi_device_id|usb_device_id|vchiq_device_id|virtio_device_id|wmi_device_id|x86_(cpu|device)_id|zorro_device_id|cpu_feature)\>" drivers/greybus/es2.c:static const struct usb_device_id id_table[] = { drivers/greybus/es2.c: const struct usb_device_id *id) drivers/greybus/gb-beagleplay.c:static const struct of_device_id gb_beagleplay_of_match[] = { drivers/staging/greybus/arche-platform.c:static const struct of_device_id arche_platform_of_match[] = { drivers/greybus/es2.c includes , drivers/greybus/gb-beagleplay.c includes which provides of_device_id via , similar drivers/staging/greybus/arche-platform.c includes which also provides of_device_id. So the #include can go away without further adaption. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/80964227feede2d8f1978f13f4219fcf9e63d8d0.1783354012.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- include/linux/greybus/greybus_id.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/include/linux/greybus/greybus_id.h b/include/linux/greybus/greybus_id.h index f4c8440093e4..72f330a35569 100644 --- a/include/linux/greybus/greybus_id.h +++ b/include/linux/greybus/greybus_id.h @@ -1,14 +1,12 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* FIXME - * move this to include/linux/mod_devicetable.h when merging + * move this to include/linux/device-id/greybus.h when merging */ #ifndef __LINUX_GREYBUS_ID_H #define __LINUX_GREYBUS_ID_H #include -#include - struct greybus_bundle_id { __u16 match_flags; From 919d1ba86be65fc08a83c5def5cc08a61a5ace62 Mon Sep 17 00:00:00 2001 From: Griffin Kroah-Hartman Date: Thu, 9 Jul 2026 15:16:40 +0200 Subject: [PATCH 130/135] mailbox: mchp-ipc-sbi: Add null check for devm_kasprintf() Add a check to see if devm_kasprintf() is not NULL in mchp_ipc_get_cluster_aggr_irq(), returning -ENOMEM if the function failed. Assisted-by: gkh_clanker_t1000 CC: Jassi Brar Signed-off-by: Griffin Kroah-Hartman Link: https://patch.msgid.link/20260709131640.210914-1-griffin@kroah.com Signed-off-by: Greg Kroah-Hartman --- drivers/mailbox/mailbox-mchp-ipc-sbi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mailbox/mailbox-mchp-ipc-sbi.c b/drivers/mailbox/mailbox-mchp-ipc-sbi.c index b87bf2fb4b9b..f081f8a9bcf8 100644 --- a/drivers/mailbox/mailbox-mchp-ipc-sbi.c +++ b/drivers/mailbox/mailbox-mchp-ipc-sbi.c @@ -378,6 +378,8 @@ static int mchp_ipc_get_cluster_aggr_irq(struct mchp_ipc_sbi_mbox *ipc) for_each_online_cpu(cpuid) { hartid = cpuid_to_hartid_map(cpuid); irq_name = devm_kasprintf(ipc->dev, GFP_KERNEL, "hart-%lu", hartid); + if (!irq_name) + return -ENOMEM; ret = platform_get_irq_byname_optional(pdev, irq_name); if (ret <= 0) continue; From f5af7132db239c5d13f20b7ac01db62b62830fb1 Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Tue, 23 Jun 2026 10:21:41 +0100 Subject: [PATCH 131/135] virtio_console: fix endian conversion in handle_control_message() There are a couple of prints in handle_control_message() which should have converted cpkt->id through virtio32_to_cpu() before passing to a print. This fixes the following (prototype) sparse warnings: drivers/char/virtio_console.c:1538:17: warning: incorrect type in argument 4 (different base types) drivers/char/virtio_console.c:1538:17: expected unsigned int drivers/char/virtio_console.c:1538:17: got restricted __virtio32 [usertype] id drivers/char/virtio_console.c:1553:25: warning: incorrect type in argument 3 (different base types) drivers/char/virtio_console.c:1553:25: expected unsigned int drivers/char/virtio_console.c:1553:25: got restricted __virtio32 [usertype] id Signed-off-by: Ben Dooks Acked-by: Arnd Bergmann Reviewed-by: Amit Shah Link: https://patch.msgid.link/20260623092141.631355-1-ben.dooks@codethink.co.uk Signed-off-by: Greg Kroah-Hartman --- drivers/char/virtio_console.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c index 198b97314168..cbdc497f5160 100644 --- a/drivers/char/virtio_console.c +++ b/drivers/char/virtio_console.c @@ -1536,7 +1536,8 @@ static void handle_control_message(struct virtio_device *vdev, cpkt->event != cpu_to_virtio16(vdev, VIRTIO_CONSOLE_PORT_ADD)) { /* No valid header at start of buffer. Drop it. */ dev_dbg(&portdev->vdev->dev, - "Invalid index %u in control packet\n", cpkt->id); + "Invalid index %u in control packet\n", + virtio32_to_cpu(vdev, cpkt->id)); return; } @@ -1553,7 +1554,8 @@ static void handle_control_message(struct virtio_device *vdev, dev_warn(&portdev->vdev->dev, "Request for adding port with " "out-of-bound id %u, max. supported id: %u\n", - cpkt->id, portdev->max_nr_ports - 1); + virtio32_to_cpu(vdev, cpkt->id), + portdev->max_nr_ports - 1); break; } add_port(portdev, virtio32_to_cpu(vdev, cpkt->id)); From e7e12b4cc0f0c3a2782aea084d4215e23f5512b3 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Tue, 23 Jun 2026 17:55:55 +0900 Subject: [PATCH 132/135] char: xilinx_hwicap: unregister class on init errors hwicap_module_init() registers icap_class before reserving the character-device region and registering the platform driver. If either of those later steps fails, the init path must undo the successful class registration before returning an error. Route the chrdev registration failure through a class unwind label, and let the platform-driver registration failure fall through the existing chrdev unwind before unregistering the class. The normal module exit path is unchanged. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: ef141a0bb0dc ("[POWERPC] Xilinx: hwicap driver") Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Reviewed-by: Radhey Shyam Pandey Link: https://patch.msgid.link/20260623085604.89284-1-mhun512@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/char/xilinx_hwicap/xilinx_hwicap.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.c b/drivers/char/xilinx_hwicap/xilinx_hwicap.c index 34a345dc5e72..9bb5fa642fd8 100644 --- a/drivers/char/xilinx_hwicap/xilinx_hwicap.c +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.c @@ -760,7 +760,7 @@ static int __init hwicap_module_init(void) HWICAP_DEVICES, DRIVER_NAME); if (retval < 0) - return retval; + goto failed_class; retval = platform_driver_register(&hwicap_platform_driver); if (retval) @@ -771,6 +771,9 @@ static int __init hwicap_module_init(void) failed: unregister_chrdev_region(devt, HWICAP_DEVICES); + failed_class: + class_unregister(&icap_class); + return retval; } From e798d274c9fc70c9d2335fce9b7aae7722b42782 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Fri, 10 Jul 2026 18:53:06 +0800 Subject: [PATCH 133/135] hwrng: drivers - Remove redundant dev_err()/dev_err_probe() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() and dev_err_probe() calls. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260710105318.376496-2-panchuang@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/char/hw_random/airoha-trng.c | 4 +--- drivers/char/hw_random/cctrng.c | 2 +- drivers/char/hw_random/imx-rngc.c | 2 +- drivers/char/hw_random/jh7110-trng.c | 3 +-- drivers/char/hw_random/omap-rng.c | 5 +---- drivers/char/hw_random/xgene-rng.c | 2 +- 6 files changed, 6 insertions(+), 12 deletions(-) diff --git a/drivers/char/hw_random/airoha-trng.c b/drivers/char/hw_random/airoha-trng.c index 076519a2f100..98c131ee9891 100644 --- a/drivers/char/hw_random/airoha-trng.c +++ b/drivers/char/hw_random/airoha-trng.c @@ -186,10 +186,8 @@ static int airoha_trng_probe(struct platform_device *pdev) airoha_trng_irq_mask(trng); ret = devm_request_irq(&pdev->dev, irq, airoha_trng_irq, 0, pdev->name, (void *)trng); - if (ret) { - dev_err(dev, "Can't get interrupt working.\n"); + if (ret) return ret; - } init_completion(&trng->rng_op_done); diff --git a/drivers/char/hw_random/cctrng.c b/drivers/char/hw_random/cctrng.c index a5be9258037f..a6925211c3b5 100644 --- a/drivers/char/hw_random/cctrng.c +++ b/drivers/char/hw_random/cctrng.c @@ -509,7 +509,7 @@ static int cctrng_probe(struct platform_device *pdev) /* register the driver isr function */ rc = devm_request_irq(dev, irq, cc_isr, IRQF_SHARED, "cctrng", drvdata); if (rc) - return dev_err_probe(dev, rc, "Could not register to interrupt %d\n", irq); + return rc; dev_dbg(dev, "Registered to IRQ: %d\n", irq); /* Clear all pending interrupts */ diff --git a/drivers/char/hw_random/imx-rngc.c b/drivers/char/hw_random/imx-rngc.c index 28c56c2d1bf6..bae8cdca13fe 100644 --- a/drivers/char/hw_random/imx-rngc.c +++ b/drivers/char/hw_random/imx-rngc.c @@ -296,7 +296,7 @@ static int __init imx_rngc_probe(struct platform_device *pdev) irq, imx_rngc_irq, 0, pdev->name, (void *)rngc); if (ret) { clk_disable_unprepare(rngc->clk); - return dev_err_probe(&pdev->dev, ret, "Can't get interrupt working.\n"); + return ret; } if (self_test) { diff --git a/drivers/char/hw_random/jh7110-trng.c b/drivers/char/hw_random/jh7110-trng.c index 4712c3c530e4..aee12caab578 100644 --- a/drivers/char/hw_random/jh7110-trng.c +++ b/drivers/char/hw_random/jh7110-trng.c @@ -303,8 +303,7 @@ static int starfive_trng_probe(struct platform_device *pdev) ret = devm_request_irq(&pdev->dev, irq, starfive_trng_irq, 0, pdev->name, (void *)trng); if (ret) - return dev_err_probe(&pdev->dev, ret, - "Failed to register interrupt handler\n"); + return ret; trng->hclk = devm_clk_get(&pdev->dev, "hclk"); if (IS_ERR(trng->hclk)) diff --git a/drivers/char/hw_random/omap-rng.c b/drivers/char/hw_random/omap-rng.c index 5e8b50f15db7..327643ba971c 100644 --- a/drivers/char/hw_random/omap-rng.c +++ b/drivers/char/hw_random/omap-rng.c @@ -391,11 +391,8 @@ static int of_get_omap_rng_device_details(struct omap_rng_dev *priv, err = devm_request_irq(dev, irq, omap4_rng_irq, IRQF_TRIGGER_NONE, dev_name(dev), priv); - if (err) { - dev_err(dev, "unable to request irq %d, err = %d\n", - irq, err); + if (err) return err; - } /* * On OMAP4, enabling the shutdown_oflo interrupt is diff --git a/drivers/char/hw_random/xgene-rng.c b/drivers/char/hw_random/xgene-rng.c index 1f4b95341c2e..629dc85c3741 100644 --- a/drivers/char/hw_random/xgene-rng.c +++ b/drivers/char/hw_random/xgene-rng.c @@ -336,7 +336,7 @@ static int xgene_rng_probe(struct platform_device *pdev) rc = devm_request_irq(&pdev->dev, ctx->irq, xgene_rng_irq_handler, 0, dev_name(&pdev->dev), ctx); if (rc) - return dev_err_probe(&pdev->dev, rc, "Could not request RNG alarm IRQ\n"); + return rc; /* Enable IP clock */ clk = devm_clk_get_optional_enabled(&pdev->dev, NULL); From 0446c8456caad083d0aa29511757355d84051805 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Fri, 10 Jul 2026 18:53:07 +0800 Subject: [PATCH 134/135] tpm: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() calls. Signed-off-by: Pan Chuang Reviewed-by: Jarkko Sakkinen Link: https://patch.msgid.link/20260710105318.376496-3-panchuang@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/char/tpm/st33zp24/st33zp24.c | 5 +---- drivers/char/tpm/tpm_i2c_nuvoton.c | 2 -- drivers/char/tpm/tpm_tis_i2c_cr50.c | 4 +--- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/drivers/char/tpm/st33zp24/st33zp24.c b/drivers/char/tpm/st33zp24/st33zp24.c index e2b7451ea7cc..8d5179367eac 100644 --- a/drivers/char/tpm/st33zp24/st33zp24.c +++ b/drivers/char/tpm/st33zp24/st33zp24.c @@ -506,11 +506,8 @@ int st33zp24_probe(void *phy_id, const struct st33zp24_phy_ops *ops, ret = devm_request_irq(dev, irq, tpm_ioserirq_handler, IRQF_TRIGGER_HIGH, "TPM SERIRQ management", chip); - if (ret < 0) { - dev_err(&chip->dev, "TPM SERIRQ signals %d not available\n", - irq); + if (ret < 0) goto _tpm_clean_answer; - } intmask |= TPM_INTF_CMD_READY_INT | TPM_INTF_STS_VALID_INT diff --git a/drivers/char/tpm/tpm_i2c_nuvoton.c b/drivers/char/tpm/tpm_i2c_nuvoton.c index d44903b29929..129aa222cc25 100644 --- a/drivers/char/tpm/tpm_i2c_nuvoton.c +++ b/drivers/char/tpm/tpm_i2c_nuvoton.c @@ -574,8 +574,6 @@ static int i2c_nuvoton_probe(struct i2c_client *client) dev_name(&chip->dev), chip); if (rc) { - dev_err(dev, "%s() Unable to request irq: %d for use\n", - __func__, priv->irq); priv->irq = 0; } else { chip->flags |= TPM_CHIP_FLAG_IRQ; diff --git a/drivers/char/tpm/tpm_tis_i2c_cr50.c b/drivers/char/tpm/tpm_tis_i2c_cr50.c index b48cacacc066..7f828fae70d3 100644 --- a/drivers/char/tpm/tpm_tis_i2c_cr50.c +++ b/drivers/char/tpm/tpm_tis_i2c_cr50.c @@ -751,10 +751,8 @@ static int tpm_cr50_i2c_probe(struct i2c_client *client) rc = devm_request_irq(dev, client->irq, tpm_cr50_i2c_int_handler, IRQF_TRIGGER_FALLING | IRQF_NO_AUTOEN, dev->driver->name, chip); - if (rc < 0) { - dev_err(dev, "Failed to probe IRQ %d\n", client->irq); + if (rc < 0) return rc; - } priv->irq = client->irq; } else { From 2cedf2272f1bb42471e646868ac572cc5752bd91 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Fri, 10 Jul 2026 18:53:08 +0800 Subject: [PATCH 135/135] char: xillybus: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() calls. Signed-off-by: Pan Chuang Acked-by: Eli Billauer Link: https://patch.msgid.link/20260710105318.376496-4-panchuang@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/char/xillybus/xillybus_of.c | 5 +---- drivers/char/xillybus/xillybus_pcie.c | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/drivers/char/xillybus/xillybus_of.c b/drivers/char/xillybus/xillybus_of.c index 1a1e64133315..46e1046abfca 100644 --- a/drivers/char/xillybus/xillybus_of.c +++ b/drivers/char/xillybus/xillybus_of.c @@ -55,11 +55,8 @@ static int xilly_drv_probe(struct platform_device *op) rc = devm_request_irq(dev, irq, xillybus_isr, 0, xillyname, endpoint); - if (rc) { - dev_err(endpoint->dev, - "Failed to register IRQ handler. Aborting.\n"); + if (rc) return -ENODEV; - } return xillybus_endpoint_discovery(endpoint); } diff --git a/drivers/char/xillybus/xillybus_pcie.c b/drivers/char/xillybus/xillybus_pcie.c index 9858711e3e79..32064b6c7627 100644 --- a/drivers/char/xillybus/xillybus_pcie.c +++ b/drivers/char/xillybus/xillybus_pcie.c @@ -83,11 +83,8 @@ static int xilly_probe(struct pci_dev *pdev, } rc = devm_request_irq(&pdev->dev, pdev->irq, xillybus_isr, 0, xillyname, endpoint); - if (rc) { - dev_err(endpoint->dev, - "Failed to register MSI handler. Aborting.\n"); + if (rc) return -ENODEV; - } /* * Some (old and buggy?) hardware drops 64-bit addressed PCIe packets,